Video tutorial coming soon...
Plugins are a way to extend Vemto functionality. They can be used to perform numerous activities such as:
A) Add a new CSS theme (example: AdminLTE plugin)
B) Modify code generation (example: Multi-Tenancy plugin)
C) Modify templates (you can modify a template in the Templates Editor, and then put it in a plugin to use in all your projects)
D) Generate files that Vemto does not generate by default (eg generate a Postman collection, generate Laravel NOVA files, etc.)
E) Execute specific commands before or after code generation
What limits what plugins can do is creativity.
You can see some plugins code examples on the following links:
1) https://github.com/TiagoSilvaPereira/vemto-simple-multitenancy-plugin
2) https://github.com/TiagoSilvaPereira/vemto-admin-lte-plugin
3) https://github.com/TiagoSilvaPereira/vemto-commands-plugin
4) https://github.com/TiagoSilvaPereira/vemto-multi-tenancy-plugin
There are some important considerations for you to know before you start creating plugins:
this.pluginData
in the template's VET codeBefore starting to create your Vemto plugin, please be sure to install:
The first step is downloading the Plugin Base from the Vemto Marketplace:
After that, extract the ZIP file to the /plugins
folder, inside the Vemto data path, and rename the extracted folder to match your plugin name (see the plugin names convention right below).
By default, you can find this folder under the following directories:
If the /plugins
path does not exist, you can create it manually (it means you never installed a plugin on Vemto before).
This extracted folder would be your plugin folder, and now you can start modifying it to have the wanted behavior.
If you want to publish your plugin in the future on Vemto Marketplace, you need to create a GitHub repository. We recommend naming your repository with the following pattern:
vemto-[PLUGIN-NAME-HERE]-plugin
For example:
- vemto-adminlte-plugin
- vemto-laravel-nova-plugin
Each plugin version must have a separate branch. For example, version 1.0.0 is in the 1.0.0 branch (Vemto will use the branch name to download the file via the marketplace). Versioning must be done using the SEMVER pattern, with three digits separated by dots.
It is also recommended to always keep the latest plugin version as the default branch on GitHub.
The README.md file is very important, as it will show the instructions on how to use your plugin, and it will be shown on the Vemto marketplace.
You can see some examples here:
Now, let's configure the package.json file to reflect the plugin data. It's a very simple and intuitive file, and it's a standard Node.js module file. Only a few settings are specific to Vemto, such as title, minVemtoVersion, and image.
A very important piece of data is the plugin id, especially when distributing it on the marketplace. The recommended pattern for creating it is to use its domain or inverted name at first, and then the plugin name. Some examples:
app.vemto.plugin-base // vemto.app
rodrigues.tiago.multitenancy // Tiago Rodrigues
app.vemto.adminlte // vemto.app
Another important data is minVemtoVersion, which defines the minimum Vemto version for plugin installation. This is very useful for creating plugins that use newer API methods, preventing them from being installed in versions that cannot execute them.
Here you can see the complete package.json file for a new plugin (the image field can be kept null for now, it is not used in Vemto yet, but it will be in the future).
{
"version": "1.0.0",
"id": "app.vemto.plugin-base",
"name": "plugin-base",
"title": "Plugin Base",
"description": "A base for creating Vemto plugins",
"image": "",
"main": "src/main.js",
"vueComponent": "dist/plugin-base.umd.min.js",
"repository": {
"type": "git",
"url": "https://github.com/TiagoSilvaPereira/vemto-plugin-base"
},
"minVemtoVersion": "1.0.0"
}
There are two other very important items on the package.json file:
"main": "src/main.js",
"vueComponent": "dist/plugin-base.umd.min.js",
Both will be better explained in the section below.
{danger} Plugins are, in essence, node.js packages and therefore support installing third-party modules usually with npm install or yarn install. However, be careful when choosing packages if you want to distribute your plugin on the Marketplace, as problematic and/or malicious packages will not be accepted.
The src/main.js file is responsible for controlling what the plugin will do at certain times during its execution. Each of these moments is called a Hook, and they correspond to methods that can be called inside main.js.
As you can see, this file always receives an instance of the Vemto API, which will be used later, inside the Hooks, to interact with Vemto:
module.exports = (vemto) => {
// ...
Most of the time, your plugin will use this file a lot, in conjunction with the Vue component explained in the next section. But in some types of plugins, where you will only read the project data and present it visually using the Vue component (for example, a plugin that shows the number of models, cruds, and files of a project in the same location), this file does not will be used a lot. In this case, you can keep it in its simplest version, just checking the installation authorization using the canInstall Hook:
module.exports = (vemto) => {
return {
canInstall() {
return true
},
}
}
You can declare all Hooks you need to use inside your plugin main.js file like below:
module.exports = (vemto) => {
return {
canInstall() {
// ...
},
onInstall() {
// ...
},
beforeCodeGenerationStart() {
// ...
},
// Etc...
}
}
{info} After changing the main.js file, it may be necessary to perform a "Reload" of the plugin in Vemto so that the cache is cleared and the changes take effect.
Now let's get to know the Hooks available for use within main.js:
Defines if the plugin can be installed. When a specific condition is not met, it should return false and use the addBlockReason API method to display a message on the lock screen. If your plugin doesn't have installation conditions, just return true. Example:
canInstall() {
let projectSettings = vemto.getProjectSettings()
if(projectSettings.cssFramework !== 'bootstrap') {
vemto.addBlockReason('This plugin is only compatible with Bootstrap projects')
return false
}
if(projectSettings.uiTemplate !== 'laravel_ui') {
vemto.addBlockReason('This plugin is only compatible with Laravel UI templates')
return false
}
return true
}
Run something as soon as the plugin is installed. It is usually used to save the initial plugin data in the project (this data will be available for all plugin Hooks and in the Vue component using the getPluginData API method). Example:
onInstall() {
vemto.savePluginData({
sidebarMode: 'dark',
sidebarCollapsed: true,
})
},
Allows to edit the composer.json file as a Javascript Object. It is util to install/uninstall composer packages during the code generation. Example:
composerPackages(packages) {
// Adding packages
packages.requireDev['phpunit/phpunit'] = '^9.5'
packages.require['spatie/laravel-multitenancy'] = '^1.0'
// Removing packages
delete packages.require['spatie/laravel-multitenancy']
return packages
},
{info} This method was added on Vemto 1.0.1
Allows to edit the packages.json file as a Javascript Object. It is util to install/uninstall Node packages during the code generation. Example:
nodePackages(packages) {
// Adding packages
packages.dependencies['moment'] = '^2.0'
packages.devDependencies['bootstrap'] = '^4.0.0'
// Removing packages
delete packages.devDependencies['bootstrap']
return packages
},
{info} This method was added on Vemto 1.0.1
Executes something just before code generation starts (for example a command, or the writing of some important file, or even a message in the log). Some Examples:
beforeCodeGenerationStart() {
vemto.log.info('Starting code generation...')
},
// Checking if there is an irregular tenancy model and aborting the code generation
beforeCodeGenerationStart() {
let data = vemto.getPluginData(),
models = vemto.getProjectModels()
models.forEach(model => {
if(this.isModelOwnedByTenant(model) && !model.hasFieldByName(data.tenantFieldName)) {
vemto.log.error(`[TENANCY ERROR] Model ${model.name} does not have a field ${data.tenantFieldName}`)
vemto.generator.abort()
}
})
},
Works basically the same way as beforeCodeGenerationStart, but it runs right after code generation starts. Example:
afterCodeGenerationStart() {
vemto.log.info('Code generation already started...')
},
This is a very important Hook, as it allows you to manipulate the code generation of a template right before it is rendered and turned into a file.
It runs for all templates, and always follows the pattern beforeRender{TemplateName}. For example:
beforeRenderModel
beforeRenderController
beforeRenderAuthServiceProvider
beforeRenderIndexView
If you have doubts about the name of the method for a certain template, you can view it from the Template Editor, as shown in the image below:
This Hook always receives two parameters:
{info} In case you are confused about the difference between compiled and rendered templates... we call compiled the template that has already gone through the VET, and rendered, the one that already has the final content to be transformed into a file
Let's illustrate the use. Assuming you want to add a PHP class to all controllers generated by Vemto, you could do something like:
beforeRenderController(template, content) {
let phpFile = vemto.parsePhp(content)
phpFile.addUseStatement('App\\MyClass')
return phpFile.getCode()
}
This Hook is called at the right time so that you can change Vemto's default templates using the replaceTemplate API method.
templateReplacements() {
vemto.log.message('Replacing stubs for AdminLTE...')
// Replace a Vemto default template
vemto.replaceTemplate(`Model.vemtl`, 'files/Model.vemtl')
}
You can use this Hook to copy files into the Laravel project generated by Vemto. It must return an array of objects, where each object has the following properties:
/files
)Example:
copyableFiles() {
return [
{from: 'files/MyClass.php', to: 'app/Classes/MyClass.php'},
{from: 'files/MyOtherClass.php', to: 'app/Classes/MyOtherClass.php'},
]
}
Executes something just before code generation ends (for example a command, or the writing of some important file, or even a message in the log). Some Examples:
beforeCodeGenerationEnd() {
vemto.log.info('Code generation will finish...')
},
beforeCodeGenerationEnd() {
vemto.executeCommand('php artisan migrate')
},
beforeCodeGenerationEnd() {
let options = {
formatAs: 'php'
}
vemto.log.message('Generating tenancy files...')
vemto.renderTemplate('files/TenantScope.vemtl', 'app/Tenancy/TenantScope.php', options)
}
Executes something just before code generation ends (for example a command, or the writing of some important file, or even a message in the log). Some Examples:
afterCodeGenerationEnd() {
vemto.log.info('Code generation finished...')
},
This Hook is only used to add marks to Schema Editor entities, using the addEntityMarker API method. Example:
addEntityMarkers() {
let models = vemto.getProjectModels()
models.forEach(model => {
if(model.name !== 'User') return
vemto.addEntityMarker(model, 'User Model', '#ECC94B')
})
},
This code will add the following tag:
Executes something just before running the project (for example, if you want to start another server, or migrate something from a custom package). Example:
beforeRunnerStart() {
vemto.log.info('Will run the project...')
vemto.executeArtisan('migrate:fresh')
},
This hook is also important if you want to disable the default commands that are fired when you run the project:
beforeRunnerStart() {
// Disable the php artisan serve command
vemto.disableDefaultRunnerServer()
// Disable the php artisan migrate command
vemto.disableDefaultRunnerMigrations()
// Disable opening the application url on the browser
vemto.disableDefaultRunnerWebPageTrigger()
},
{info} This method was added on Vemto 1.0.1
Executes something just before finishing to run the project. Example:
beforeRunnerEnd() {
vemto.openLink('http://localhost:8000')
},
{info} This method was added on Vemto 1.0.1
A plugin's Vue component (usually Component.vue) is used to create a visual interface for the plugin. If your plugin doesn't need a visual interface, you can simply add a simple message like "Plugin installed and working..." to this component.
It's a common Vue component as you may already know, but it has access to some features of the Vemto API.
Below, let's learn about some of these features:
Vemto uses the TailwindCSS framework to build its entire interface. By creating a visual interface for your plugin, you will automatically be using it. However, there are some ready-made classes according to the Vemto interface that you can use:
{info} Maintaining the plugin's visual consistency with the Vemto interface makes it easier to approve it for the Marketplace.
The API is also available within the Vue component. However, since the visual part of the plugin does not have access to Hooks, it is not possible to use methods to generate or replace files, or to run on other important Hooks such as addEntityMarkers.
Tasks like: changing files and templates, writing files, etc. are always done in main.js.
One of the most common uses of the API within the Vue component is to get the data through the getPluginData method and to save it later using the savePluginData method.
Example:
data() {
return {
text: {},
}
},
created() {
let pluginData = window.vemtoApi.getPluginData()
this.text = pluginData.sidebarMode
},
methods: {
save() {
window.vemtoApi.savePluginData({
text: this.text,
})
}
}
Another common use is to debounce when saving data (for example, when you have an input and want to save the data while someone is typing). In order not to have to install a debounce library, Vemto already makes it available for you:
methods: {
// 500 ms debounce to save the data
save: window.vemtoApi.debounce(function() {
window.vemtoApi.savePluginData({
text: this.text
})
}, 500)
}
{info} Always place a debounce if you want to save data as you type. This will facilitate the approval of your plugin on the Marketplace, and it helps a lot in the general performance of Vemto.
While creating your component, and after, publishing the component, you will need to compile it so that it will be recognized by Vemto. It is not possible to directly include the Component.vue file, it must first go through a process that transforms it into a library.
{warning} Due to the new Vue CLI (@vue/cli) deprecating the old vue-cli, the command vue build ./src/Component.vue -t lib will not work to build the component, unless you are still using the old Vue CLI (in that case, just run this command inside the plugin folder). So, if you are using newest Vue CLI versions, please follow the steps below.
Firstly, install the new Vue CLI and the Vue CLI Service globally:
-- Removing the old Vue CLI
npm uninstall vue-cli -g
-- Installing Vue CLI globally
npm install -g @vue/cli
-- Installing the Vue CLI service
npm install -g @vue/cli-service
-- Installing the Vue template compiler globally (For Vue 2)
npm i -g vue-template-compiler
-- Installing the Vue template compiler globally (For Vue 3)
npm i -g @vue/compiler-sfc
After installing all the necessary stuff, just run the following command inside your plugin folder:
vue-cli-service build ./src/Component.vue --target lib --inline-vue
As you can see, we are using the --target lib argument. This allows third-party packages included in Component.vue to be compiled along with your code, making it possible to create plugins that go beyond the possibilities of the Vemto API.
After the first compilation, be sure to add the compiled and minified component that is in the /dist folder in your package.json settings, if you haven't already:
{
"vueComponent": "dist/my-plugin.umd.min.js"
}
{info} After a compilation, it may be necessary to perform a "Reload" of the plugin in Vemto so that the cache is cleared and the changes take effect.
If you want to show messages while creating your plugin component, you may use the Plugin Console to do it.
You can access the console methods using the window.vemtoApi.pluginConsole helper inside your Vue components. For example:
window.vemtoApi.pluginConsole.log('Some message here')
window.vemtoApi.pluginConsole.error('Something went wrong')
// The .error method supports both errors and string messages
try {
// ...
} catch (error) {
window.vemtoApi.pluginConsole.error(error)
window.vemtoApi.pluginConsole.error('string message')
}
While we don't have a plugin submission form yet, you can send an email to [email protected] with the link to your GitHub repository for us to review and add your plugin to the Marketplace.
To date, we do not support paid plugins. If you are interested in distributing a plugin for a fee, please contact us so we can discuss the options.