Laravel Nova: Adding custom buttons to resource toolbars

If you’ve seen issue 786 on laravel/nova-issues on Github or attempted to add custom buttons to Resources in Laravel Nova you are probably frustrated. I spent a whole day trying to figure this out. But it’s a lot simpler than you think!

Laravel Nova is powered by Vue.JS a very powerful JS microframework. Each resource has a custom component and that component has its own scope. This allows us to override Nova’s built-in components and add custom buttons.

Let’s start by creating a new Nova Resource Tool:

php artisan nova:resource-tool 0x15f/custom-resource-toolbar

Say yes to all of the prompts…

Now that you’ve created your resource tool navigate to the nova-components/custom-resource-toolbar/resources/js directory open tool.js in your favorite JS editor and paste the following.

Nova.booting((Vue, router) => {
    Vue.component('custom-detail-toolbar', require('./components/CustomDetailToolbar'));
    Vue.component('quotes-detail-toolbar', require('./components/QuotesDetailToolbar'));
})

Navigate into the components directory and delete Tool.vue. Now create two files, one named CustomDetailToolbar.vue the other named QuotesDetailToolbar.vue. Paste the following into CustomDetailToolbar.vue:

<template>
    <div class="flex w-full justify-end items-center mx-3">
        <component v-if="hasComponent" :is="component" />
    </div>
</template>
<script>
    export default {
        props: ['resourceName', 'resourceId'],
        computed: {
            component(){
                return this.resourceName + '-detail-toolbar';
            },
            hasComponent()
            {
                return this.component in this.$options.components;
            }
        }
    }
</script>

And paste the following into QuotesDetailToolbar.vue:

<template>
    <div>
        <div class="flex w-full justify-end items-center">
           <a
                class="btn btn-default btn-icon btn-white"
                :href="'/nova-vendor/custom-resource-toolbar/export-quote/' + this.$parent.resourceId"
                style="background-color: var(--primary); color: white;">
                Export as PDF
             </a>
        </div>
    </div>
</template>
<script>
    export default {
        props: ['resourceName', 'resourceId', 'field'],
        mounted() {
            var elements = document.getElementById('nova').querySelectorAll('h4');
            [].forEach.call(elements, function(element) {
                if(element.innerHTML === 'Custom Detail Toolbar') {
                    element.parentNode.remove();
                }
            });
        }
    }
</script>

You can now build your Resource Tool using npm run watch, add it to your Resources, and open your CustomResourceToolbar.php file and change the component name to custom-detail-toolbar. I’ll explain what we did below.

In tool.js we registered two new components custom-detail-toolbar and quotes-detail-toolbar, custom-detail-toolbar is used by Nova to load any components that should be displayed on that toolbar. Nova then looks for the resource’s toolbar. You can define your resources toolbar component by registering a component with your resources plural name followed by -detail-toolbar. Within that component, you can add any CSS/JS that should be placed in the toolbar.

If you notice I have a mounted function in my component with the following code:

mounted() {
    var elements = document.getElementById('nova').querySelectorAll('h4');
    [].forEach.call(elements, function(element) {
        if(element.innerHTML === 'Custom Detail Toolbar') {
            element.parentNode.remove();
        }
    });
}

Typically a resource tool has a panel on your resource detail page. This function runs when our component has been mounted to remove the panel that nova adds giving your page a clean feel.