amjadiqbal / vueforge-plugin
The rapid Vue 3 component & widget engine for October CMS backend interfaces.
Package info
github.com/amjadiqbal/oc-vueforge-plugin
Type:october-plugin
pkg:composer/amjadiqbal/vueforge-plugin
Requires
- php: >=8.2
- composer/installers: ^1.0 || ^2.0
- october/rain: >=4.2
Requires (Dev)
- phpunit/phpunit: ^10.0 || ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
VueForge for October CMS
The rapid Vue 3 component & widget engine for October CMS backend interfaces.
VueForge lets you drop a real Vue 3 .vue single-file component (Composition
API, <script setup>, TypeScript, compiled by Vite) into any October CMS
backend form as a FormWidget - with prop serialization, JSON save/load, and
a bi-directional v-model-style sync to the form's native HTML submit
already wired up. It is a deliberate alternative to October's own native
VueComponentBase system - see docs/VUE3_MIGRATION_GUIDE.md for how the two
differ and when to use which.
Requirements
- October CMS 4.2+ (built and tested against 4.4.5,
october/rain^4.4) - PHP 8.2+
- Node 18+ / npm, for building the frontend assets (Vite 5/6, Vue 3.4+, TypeScript)
Quickstart
-
Install the plugin into
plugins/amjad/vueforge(see the Installation section below). -
Build its frontend assets once:
cd plugins/amjad/vueforge npm install npm run build -
Use the
vueforgewidget type in anyfields.yaml:tags: label: Tags type: vueforge component: TagInput props: placeholder: "Add a tag..." metadata: label: Metadata type: vueforge component: JsonEditor
The field's underlying model attribute should be a JSON-castable column (
protected $jsonable = ['tags', 'metadata'];on the model, or a native JSON column).
That's it - TagInput (array state) and JsonEditor (nested key/value
object state) ship as working examples in
assets/vue/components/.
Architecture
formwidgets/VueWidget.php FormWidgetBase subclass: renders the mount
point, serializes props, sanitizes save data.
classes/ViteResolver.php Resolves the right asset (dev server or built
manifest.json chunk) for a widget's Vite entry.
assets/js/vueforge.ts ESM hydrator: finds [data-vueforge-widget]
elements, dynamically imports and mounts the
matching component.
assets/js/composables/
useVueForge.ts Bi-directional v-model <-> hidden <input> sync.
useOctoberAjax.ts Typed wrapper around October's window.jax AJAX API.
assets/vue/components/
TagInput.vue Example: array state (chips/tags).
JsonEditor.vue Example: nested key/value object state.
console/MakeVueWidget.php `php artisan vueforge:make` generator.
How a value round-trips
- Load:
VueWidget::prepareVars()reads the model attribute via October's normalgetLoadValue(), JSON-encodes it (HTML-attribute-escaped- see the migration guide, item 9), and renders it into
data-vueforge-propsplus a seed<input type="hidden" value="...">.
- see the migration guide, item 9), and renders it into
- Hydrate:
vueforge.tsfinds the mount point, dynamically imports the named component, and mounts it with the parsed props. - Edit: the component uses
useVueForge()to get a reactivevalueref; every change re-serializes it into the hidden input'svalue. - Save: October's normal form submit posts the hidden input's JSON
string;
VueWidget::getSaveValue()parses and sanitizes it (rejecting malformed JSON and stripping anything that isn't plain array/scalar data) before Eloquent persists it.
YAML field configuration reference
| Key | Type | Default | Description |
|---|---|---|---|
component |
string | JsonEditor |
The Vue component to mount. Must be registered in vueforge.ts's component map (or via registerVueForgeComponent() for your own components). |
viteEntry |
string | assets/vue/components/{component}.vue |
Source path used to look up the built asset in Vite's manifest. |
props |
array | [] |
Extra static props merged with the field's current value (passed as modelValue) before being handed to the component. |
Artisan CLI: scaffolding your own widget
php artisan vueforge:make Acme.Blog TagList
Generates:
plugins/acme/blog/formwidgets/TagList.php- aVueWidgetsubclass with$component/$viteEntrypre-filled.plugins/acme/blog/assets/vue/TagList.vue- a<script setup lang="ts">stub already wired touseVueForge().
and prints the YAML type: code to use. Register the generated FormWidget in
your plugin's Plugin::registerFormWidgets() as usual.
Note: the generated component imports useVueForge via a relative path
across plugin folders. This only resolves in your own plugin's Vite build if
its dev server is configured with server.fs.allow including the VueForge
plugin's directory (Vite restricts serving files outside its project root by
default) - see docs/VUE3_MIGRATION_GUIDE.md.
Installation
Plugin code: Amjad.VueForge (matches the registered October CMS Marketplace
author code exactly - confirmed by a real rejected submission: "Supplied
plugin code 'AmjadIqbal' does not match author code of 'Amjad'").
Not published to Packagist.org - the amjad vendor there is already
taken by an unrelated package, and publishing under a different vendor would
install this plugin at the wrong directory (composer/installers derives
the install path only from the Composer package's own vendor prefix, with
no override) and silently fail to load, since October's plugin loader then
looks for a class that wouldn't exist at that path. This doesn't affect
distribution through October CMS itself:
Via the October Marketplace (once the listing is live):
php artisan plugin:install Amjad.VueForge
Manual install (works today, no Marketplace approval needed):
- Clone this repository directly into your October CMS application's
plugins/amjad/vueforgedirectory (i.e. this repo's root becomes that directory - do not nest it any further). cd plugins/amjad/vueforge && npm install && npm run build.php artisan october:migrate(no migrations ship with this plugin, but this refreshes the plugin registry so the widget/console command appear).
composer.json's package name (amjad/vueforge-plugin) is retained for
October's own internal package metadata but is not registered on public
Packagist.org - see CHANGELOG.md (0.1.7) for the full reasoning if you're
wondering why composer require isn't listed as an install method here.
Testing
- PHP:
vendor/bin/phpunitfrom your October application root, scoped to this plugin'stests/directory (see the docblock intests/VueWidgetTest.phpfor the exact invocation and why a bareBackend\Classes\FormField+VueWidgetpair is used instead of a fullBackend\Widgets\Form). - TypeScript:
npm run typecheck(vue-tsc --noEmit). - JS component behavior:
npm test(Vitest +@vue/test-utils+ jsdom) - covers mount/prop-hydration/hidden-input sync/unmount for both example components. - Build:
npm run build(Vite production build; fails the wholebuildscript ifvue-tsc --noEmitreports errors first).
Why assets/dist/ is not committed
The built Vite output (assets/dist/, including manifest.json) is
.gitignored. ViteResolver requires a real build to resolve production
assets, so run npm run build after installing this plugin - there is no
built-in fallback. This mirrors how most modern Vite-based October plugins in
the ecosystem ship (source in git, build as an install step), and avoids
committing machine-generated, hashed filenames that differ per Node/Vite
version.
License
MIT - see LICENSE.