You are viewing the documentation for an older version of this package. You can check the version you are using with the following command:
composer show spatie/laravel-medialibrary
Handling uploads with Vue
On this page
Media Library Pro provides beautiful UI components for Vue 2 and Vue 3. They pack a lot of features: temporary uploads, custom property inputs, frontend validation, and robust error handling.
The MediaLibraryAttachment
component can upload one or more files with little or no extra information. The attachment component is a lightweight solution for small bits of UI like avatar fields.
The MediaLibraryCollection
component can upload multiple files with custom properties. The collection component shines when you need to manage media, like in backoffices.
If neither of these fit the bill, we've exposed a set of APIs for you to be bold and roll your own components.
##Demo application
In this repo on GitHub, you'll find a demo Laravel application in which you'll find examples of how to use Media Library Pro with Vue.
If you are having troubles using the components, take a look in that app to see how we've done it.
##Basic setup
First, the server needs to be able to catch your incoming uploads. Use the mediaLibrary
macro in your routes file.
// Probably routes/web.php Route::mediaLibrary();
The macro will register a route on /media-library-pro/uploads
, which is used by the Vue components by default. You can change the prefix by passing it to the macro:
// Probably routes/web.php Route::mediaLibrary('my-custom-url');
This will register a route at /my-custom-url/uploads
instead.
##Customizing the upload endpoint
The Vue components post data to /media-library-pro/uploads
by default. If you changed the prefix in the route macro, pass it to the route-prefix
prop of your Vue components.
<media-library-attachment name="avatar" route-prefix="my-custom-url" />
##Importing the components
The components aren't available through npm, but are located in vendor/spatie/laravel-medialibrary-pro/resources/js
when you install the package through Composer. This makes for very long import statements, which you can clean up by adding some configuration to your Webpack/Laravel Mix configuration.
If you're developing a project where you don't have access to composer, you can download the package through GitHub Packages: installation steps
laravel-mix >6
// webpack.mix.js mix.override((webpackConfig) => { webpackConfig.resolve.modules = [ "node_modules", __dirname + "/vendor/spatie/laravel-medialibrary-pro/resources/js", ]; });
laravel-mix <6
// webpack.mix.js mix.webpackConfig({ resolve: { modules: [ "node_modules", __dirname + "/vendor/spatie/laravel-medialibrary-pro/resources/js", ], }, });
This will force Webpack to look in vendor/spatie/laravel-medialibrary-pro/resources/js
when resolving imports, and allows you to shorten your import. Notice that the Vue 2 and Vue 3 components are separate components.
import { MediaLibraryAttachment } from "media-library-pro-vue2-attachment"; // or import { MediaLibraryAttachment } from "media-library-pro-vue3-attachment";
If you're using TypeScript, you will also have to add this to your tsconfig:
// tsconfig.json { "compilerOptions": { "paths": { "*": ["*", "vendor/spatie/laravel-medialibrary-pro/resources/js/*"] } } }
To use a component in your Blade templates, import the components you plan to use in your app.js
file, and add them to your main Vue app's components
object.
Vue 2
import Vue from "vue"; import { MediaLibraryAttachment } from "media-library-pro-vue2-attachment"; import { MediaLibraryCollection } from "media-library-pro-vue2-collection"; new Vue({ el: "#app", components: { MediaLibraryAttachment, MediaLibraryCollection, }, });
Vue 3
import { createApp } from "vue"; import { MediaLibraryAttachment } from "media-library-pro-vue3-attachment"; import { MediaLibraryCollection } from "media-library-pro-vue3-collection"; createApp({ components: { MediaLibraryAttachment, MediaLibraryCollection, }, }).mount("#app");
You can now use them in any .blade.php
file in your application.
<!-- posts/edit.blade.php --> <div id="app"> <form> <media-library-attachment name="cover" /> <media-library-collection name="images" /> <button>Submit</button> </form> </div>
You may also choose to import the components on the fly in a .vue
file.
<!-- EditPost.vue --> <template> <form> <media-library-attachment name="cover" /> <media-library-collection name="images" /> <button>Submit</button> </form> </template> <script> import { MediaLibraryAttachment } from "media-library-pro-vue3-attachment"; import { MediaLibraryCollection } from "media-library-pro-vue3-collection"; export default { components: { MediaLibraryAttachment, MediaLibraryCollection, }, }; </script>
##Vite
If you are using vite, you need to import an alias to the vite.config.js
and some little changes to your Vue component.
// vite.config.js import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ ... resolve: { alias: { '@': '/resources/js', + 'spatie-media-lib-pro': '/vendor/spatie/laravel-medialibrary-pro/resources/js', }, }, });
Component changes Vue2
... - import { MediaLibraryAttachment } from "media-library-pro-vue2-attachment"; + import { MediaLibraryAttachment } from "spatie-media-lib-pro/media-library-pro-vue2-attachment"; - import { MediaLibraryCollection } from "media-library-pro-vue2-collection"; + import { MediaLibraryCollection } from "spatie-media-lib-pro/media-library-pro-vue2-collection"; ...
Component changes Vue3
... - import { MediaLibraryAttachment } from "media-library-pro-vue3-attachment"; + import { MediaLibraryAttachment } from "spatie-media-lib-pro/media-library-pro-vue3-attachment"; - import { MediaLibraryCollection } from "media-library-pro-vue3-collection"; + import { MediaLibraryCollection } from "spatie-media-lib-pro/media-library-pro-vue3-collection"; ...
CSS Import for SPA use
If you are using a SPA you can import the CSS into app.js
like this:
// resources/js/app.js import './bootstrap'; import '../css/app.css'; + import 'spatie-media-lib-pro/media-library-pro-styles/src/styles.css'; ...
If you want to import the CSS into app.css
you can still use the import mentioned in Customizing CSS.
##Your first components
The most basic components have a name
prop. This name will be used to identify the media when it's uploaded to the server.
<!-- MyImageUploader.vue --> <template> <form> <media-library-attachment name="avatar" /> <media-library-collection name="downloads" /> <button>Submit</button> </form> </template> <script> import { MediaLibraryAttachment } from "media-library-pro-vue3-attachment"; import { MediaLibraryCollection } from "media-library-pro-vue3-collection"; export default { components: { MediaLibraryAttachment, MediaLibraryCollection, }, }; </script>
##Passing an initial value
If your form modifies an existing set of media, you may pass it through in the initial-value
prop.
You can retrieve your initial values in Laravel using $yourModel->getMedia($collectionName)
. This will also take care of any old
values after an invalid form submit. You can also use this straight in your blade file:
<form> <media-library-attachment name="avatar" :initial-value="$post->getMedia('avatar')" /> <media-library-collection name="downloads" :initial-value="$post->getMedia('downloads')" /> <button>Submit</button> </form>
Under the hood, these components create hidden <input />
fields to keep track of the form values on submit. If you would like to submit your values asynchronously, refer to the Asynchronously submit data
section.
##Setting validation rules
You'll probably want to validate what gets uploaded. Use the validation-rules
prop, and don't forget to pass Laravel's validation errors too. The validation errors returned from the server will find errors under the key used in your name
prop.
<form> <media-library-attachment name="avatar" :initial-value="$post->getMedia('avatar')" :validation-rules="{ accept: ['image/png', 'image/jpeg'], maxSizeInKB: 5000 }" :validation-errors="$errors" /> <media-library-collection name="downloads" :initial-value="$post->getMedia('downloads')" :validation-rules="{ accept: ['image/png', 'image/jpeg'], maxSizeInKB: 5000 }" :validation-errors="$errors" /> <button>Submit</button> </form>
You can also set the maximum amount of images that users can be uploaded using the max-items
prop. Don't forget to set the multiple
prop for the attachment component.
<form> <media-library-attachment name="files" :max-items="2" multiple /> <media-library-collection name="downloads" :max-items="5" /> <button>Submit</button> </form>
See the Validation rules section for a complete list of all possible validation rules.
##Checking the upload state
The components keep track of whether they're ready to be submitted, you can use this to disable a submit button while a file is still uploading or when there are frontend validation errors. This value can be tracked by listening to a is-ready-to-submit-change
event on the components. If you submit a form while a file is uploading, Laravel will return a HTTP 500 error with an invalid uuid
message.
<template> <form> <media-library-attachment name="avatar" @is-ready-to-submit-change="isReadyToSubmit = $event" /> <button :disabled="!isReadyToSubmit">Submit</button> </form> </template> <script> import { MediaLibraryAttachment } from "media-library-pro-vue3-attachment"; export default { components: { MediaLibraryAttachment }, data() { return { isReadyToSubmit: true, }; }, }; </script>
##Using custom properties
The Media Library supports custom properties to be saved on a media item. The values for these can be chosen by your users. By default, the MediaLibraryAttachment
component doesn't show any input fields, and the MediaLibraryCollection
component only shows a name
field, with the option to add more fields.
Use the fields
scoped slot to add some fields:
Vue 2
<media-library-collection name="images" :initial-value="{{ $images }}"> <template slot="fields" slot-scope="{ getCustomPropertyInputProps, getCustomPropertyInputListeners, getCustomPropertyInputErrors, getNameInputProps, getNameInputListeners, getNameInputErrors, }" > <div class="media-library-properties"> <div class="media-library-field"> <label class="media-library-label">Name</label> <input class="media-library-input" v-bind="getNameInputProps()" v-on="getNameInputListeners()" /> <p v-for="error in getNameInputErrors()" :key="error" class="media-library-text-error" > @{{ error }} </p> </div> <div class="media-library-field"> <label class="media-library-label">Extra field</label> <input class="media-library-input" v-bind="getCustomPropertyInputProps('extra_field')" v-on="getCustomPropertyInputListeners('extra_field')" /> <p v-for="error in getCustomPropertyInputErrors('extra_field')" :key="error" class="media-library-text-error" > @{{ error }} </p> </div> </div> </template> </media-library-collection>
Vue 3
<media-library-collection name="images" :initial-value="{{ $images }}"> <template #fields="{ getCustomPropertyInputProps, getCustomPropertyInputListeners, getCustomPropertyInputErrors, getNameInputProps, getNameInputListeners, getNameInputErrors, }" > … (see Vue 2 example above) </template> </media-library-collection>
When you add an image to your collection, it will look like this.
##Customizing the file properties
When uploading a file, some properties appear by default: its extension, filesize and a remove or download button (respectively for the attachment or collection component).
You can customize what is displayed here by using the properties
scoped slot:
Vue 2
<media-library-attachment name="images" :initial-value="{{ $images }}" > <template slot="properties" slot-scope="{ object }"> <div class="media-library-property"> {{ object.attributes.name }} </div> </template> </media-library-collection>
Vue 3
<media-library-attachment name="images" :initial-value="{{ $images }}" > <template #properties="{ object }"> <div class="media-library-property"> {{ object.attributes.name }} </div> </template> </media-library-collection>
##Asynchronously submit data
If you don't want to use traditional form submits to send your data to the backend, you will have to keep track of the current value of the component using the onChange
handler. The syntax is the same for all UI components:
<template> <div> <media-library-attachment name="avatar" :initial-value="media" :validation-errors="validationErrors" @change="onChange" /> <media-library-collection name="media" :initial-value="media" :validation-errors="validationErrors" @change="onChange" /> <button @click="submitForm">Submit</button> </div> </template> <script> import Axios from "axios"; export default { props: { values }, data() { return { validationErrors: {}, media: this.values.media, }; }, methods: { onChange(media) { this.media = media; }, submitForm() { Axios.post("endpoint", { media: this.media }).catch( (error) => (this.validationErrors = error.data.errors) ); }, }, }; </script>
##Usage with Laravel Vapor
If you are planning on deploying your application to AWS using Laravel Vapor, you will need to do some extra configuration to make sure files are uploaded properly to an S3 bucket.
First off, make sure you have enabled Vapor support in Laravel.
You will also need to set the vapor
prop in your components.
<media-library-attachment name="media" vapor />
If you edited Vapor's signed storage URL in Laravel, you will need to pass the new endpoint to your components in vapor-signed-storage-url
. It will use /vapor/signed-storage-url
by default.
<media-library-attachment name="media" vapor vapor-signed-storage-url="/vapor/signed-storage-url" />
##Usage with Inertia
When using the components in repository that uses Inertia, the setup is very similar to the asynchronous setup.
<template> <div> <media-library-attachment name="avatar" :initial-value="avatar" :validation-errors="validationErrors" @change="onChange" /> <button @click="submitForm">Submit</button> </div> </template> <script> import { Inertia } from "@inertiajs/inertia"; export default { data() { return { validationErrors: this.$page.props.errors, avatar: this.$page.props.values.avatar, }; }, methods: { onChange(avatar) { this.avatar = avatar; }, submitForm() { Inertia.post("", { avatar: this.avatar }); }, }, }; </script>
##Validation rules
There are a couple of different ways to validate files on the frontend. These props are available to you: validationRules
, maxItems
and beforeUpload
.
validationRules
In the validationRules
object, we've got the accept
property, which expects an array of MIME types as strings. Leave it empty to accept all types of files, set its value to ['image/*']
to accept any type of image, or choose your own set of rules using MIME types. Remember, the only valid MIME type of a JPEG/JPG is image/jpeg
!
The minSizeInKB
and maxSizeInKB
properties set the minimum and maximum size of any individual file.
<media-library-attachment name="avatar" :validation-rules="{ accept: ['image/jpeg', 'image/gif', 'application/pdf'], minSizeInKB: 512, maxSizeInKB: 512, }" />
maxItems
Set the maximum amount of items in the collection/attachment component at any time.
<media-library-attachment name="avatar" :max-items="3" />
beforeUpload
Pass a method to before-upload
that accepts a file parameter. Return any value (or resolve a Promise with any value) from this function to upload the file. Throw an Error in this function to cause the file not to be uploaded, and display your error message.
<template> <media-library-attachment name="avatar" :before-upload="checkFileValidity" /> </template> <script> export default { … methods: { checkFileValidity(file) { return new Promise((resolve) => { if (file.size < 1000) { return resolve(); } throw new Error("The uploaded file is too big"); }); } }, } </script>
##Translations
If you would like to use the components in your own language, you can pass a translations
prop to the component.
<media-library-collection :translations="{ fileTypeNotAllowed: 'You must upload a file of type', tooLarge: 'File too large, max', tooSmall: 'File too small, min', tryAgain: 'please try uploading this file again', somethingWentWrong: 'Something went wrong while uploading this file', selectOrDrag: 'Select or drag files', selectOrDragMax: 'Select or drag max {maxItems} {file}', file: { singular: 'file', plural: 'files' }, anyImage: 'any image', anyVideo: 'any video', goBack: 'Go back', dropFile: 'Drop file to upload', dragHere: 'Drag file here', remove: 'Remove', download: 'Download', }" />
The values mentioned here are the defaults. Feel free to only pass in a couple of keys, as your object will be merged onto the default.
If you use the component in different parts of your app, you might want to set the translations globally.
window.mediaLibraryTranslations = { somethingWentWrong: "whoops", remove: "delete", };
If you use the vue-i18n package from intlify, you can also pass the keys from a translation file like lang/media-library.php
by using the $tm
-function.
<MediaLibraryCollection
:translations="$tm('media-library')"
/>
##Props
These props are available on both the attachment
and the collection
component.
prop name | Default value | Description |
---|---|---|
name | ||
initial-value | [] |
|
route-prefix | "media-library-pro" |
|
upload-domain | Use this if you're uploading your files to a separate (sub)domain, e.g. files.mydomain.com (leave out the trailing slash) |
|
validation-rules | Refer to the "validation rules" section | |
validation-errors | The standard Laravel validation error object | |
multiple | false (always true in the collection component) |
Only exists on the attachment components |
max-items | 1 when multiple = false , otherwise `undefined |
|
vapor | Set to true if you will deploy your application to Vapor. This enables uploading of the files to S3. | |
vapor-signed-storage-url | "vapor/signed-storage-url" |
|
max-size-for-preview-in-bytes | 5242880 (5 MB) |
When an image is added, the component will try to generate a local preview for it. This is done on the main thread, and can freeze the component and/or page for very large files |
sortable | true |
Only exists on the collection components. Allows the user to drag images to change their order, this will be reflected by a zero-based order attribute in the value |
translations | Refer to the "Translations" section | |
file-type-help-text | Override the automatically generated helptext from validation-rules.accept |
|
ref | Used to set a reference to the MediaLibrary instance, so you can change the internal state of the component. | |
before-upload | A method that is run right before a temporary upload is started. You can throw an Error from this function with a custom validation message |
|
after-upload | A method that is run right after a temporary upload has completed, { success: true, uuid } |
|
@changed | ||
@is-ready-to-submit-change | Refer to Checking the upload state section |