DISCLAIMER:
- A part of my code is AI generated since I really cant find any information for my problem
- I use Kirby 6.0.0-alpha.1 for this project
Hello,
I currently want to create a plugin for my newest Kirby project based on Kirby 6 as a field test for Vue 3 and Kirby.
I have problems uploading an image using a k-button, an input field and a JS based request.
For reference these are the relevant code parts:
The vue template:
<div class="field-card">
<k-grid style="gap: var(--spacing-4);">
<k-column width="1/2">
<label class="k-label">{{ $t('embed_footer_control.icon_light') }}</label>
<div class="upload-row">
<k-button icon="upload" @click="$refs.iconLight.click()">
{{ $t('embed_footer_control.choose_file') }}
</k-button>
<input ref="iconLight" type="file" accept="image/*" class="hidden-input"
@change="onIconUpload($event, 'light')" />
<span class="file-hint">
{{ settings.iconLightUrl ? $t('embed_footer_control.file_selected') :
$t('embed_footer_control.no_file') }}
</span>
</div>
<k-input type="text" :label="$t('embed_footer_control.public_url')" :value="settings.iconLightUrl"
readonly />
</k-column>
<k-column width="1/2">
<label class="k-label">{{ $t('embed_footer_control.icon_dark') }}</label>
<div class="upload-row">
<k-button icon="upload" @click="$refs.iconDark.click()">
{{ $t('embed_footer_control.choose_file') }}
</k-button>
<input ref="iconDark" type="file" accept="image/*" class="hidden-input"
@change="onIconUpload($event, 'dark')" />
<span class="file-hint">
{{ settings.iconDarkUrl ? $t('embed_footer_control.file_selected') :
$t('embed_footer_control.no_file') }}
</span>
</div>
<k-input type="text" :label="$t('embed_footer_control.public_url')" :value="settings.iconDarkUrl"
readonly />
</k-column>
</k-grid>
</div>
This is the function that is used to upload the image:
async onIconUpload(event, mode) {
const file = event?.target?.files?.[0];
if (!file) return;
try {
const form = new FormData();
form.append("file", file);
form.append("mode", mode);
const res = await this.$api.request("embed-footer-control/icon", {
method: "POST",
body: form,
});
const payload = await res?.json?.() ?? res?.data ?? res ?? {};
const url = payload.url;
const key = payload.key ?? (mode === "dark" ? "iconDarkUrl" : "iconLightUrl");
if (!url) throw new Error("Upload succeeded, but no URL returned");
const prev = this.iconUrlMap[mode];
if (prev && prev.startsWith("blob:")) URL.revokeObjectURL(prev);
this.settings[key] = url;
this.initialSettings[key] = url;
this.iconUrlMap[mode] = url;
this.$panel?.notification?.success?.(
this.$t("embed_footer_control.upload_success") || "Icon hochgeladen"
);
} catch (error) {
console.error(error);
this.$panel?.notification?.error?.(
this.$t("embed_footer_control.upload_failed") || "Upload fehlgeschlagen"
);
}
and this is my plugin route
[
'pattern' => 'embed-footer-control/icon',
'method' => 'POST',
'action' => function () {
$kirby = kirby();
error_log(json_encode($kirby->request()->get("mode")));
$mode = (string) ($kirby->request()->get('mode') ?? $kirby->request()->data('mode') ?? '');
if ($mode === '') {
$mode = 'light';
}
if (!in_array($mode, ['light', 'dark'], true)) {
throw new Exception('Invalid icon mode: ' . $mode);
}
$file = $kirby->request()->file('file');
if (!$file || empty($file['tmp_name'])) {
throw new Exception('No file uploaded');
}
$service = new EmbedService($kirby);
return $service->repository()->uploadIcon($file, $mode);
},
],
My problem is that I cannot access the “mode” value. It is inside the data variable but I cannot access it.
I think I have an error inside my Vue Method (this part is completly AI generated since I cannot find any useful information on the kirby doc/cookbook and reference side - maybe I overlooked something?
This is the data Array returned by kirby()->request()->data() (printed as json!)
{
"------geckoformboundary72d2665e32a21ee7ea293d0a3056c394\r\nContent-Disposition:_form-data;_name": "\"file\"; filename=\"logo_white.svg\"\nContent-Type: image/svg xml\n\n<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!-- Created with Inkscape (http://www.inkscape.org/) -->\n\n<svg\n width=\"1500\"\n height=\"1500\"\n viewBox=\"0 0 396.87499 396.875\"\n version=\"1.1\"\n id=\"svg1\"\n xml:space=\"preserve\"\n sodipodi:docname=\"logo_white.svg\"\n inkscape:version=\"1.4.2 (ebf0e940, 2025-05-08)\"\n xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"\n xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:svg=\"http://www.w3.org/2000/svg\">.\n------geckoformboundary72d2665e32a21ee7ea293d0a3056c394\nContent-Disposition: form-data; name=\"mod>\"\n\nlight\n------geckoformboundary72d2665e32a21ee7ea293d0a3056c394--\n"Disposition: form-data; name=\"mode\"\n\nlight\n------geckoformboundary72d2665e32a21ee7ea293d0a3056c394--\n"
}
It is clearly inside but I think I’m sending the request wrong…
Is there a page about the injected $api that explains how to send requests and how to handle them correctly?
And what is my error in the code above? ![]()
Thank you for the help ^^