Skip to main content

How to Develop a Plugin

Pre-requisites

Before the development of a plugin, take the following steps:

  1. Generate a service with Amplication
  2. Apply the changes that we need:
    • Add the missing functionality
    • Manipulate the existing functionality
  3. use the knowledge from the previous step to design the plugin:
    • Which events need to be used
    • How to use the events with the before and after lifecycle functions

Creating a GitHub Repository From Amplication Plugin Template Repository

  1. Go to amplication plugin template and create a GitHub repository from this template. Make sure it's a public repository. amplication-plugin-template.pngcreate-new-repo-from-template
  2. When your new repository is ready, clone it and start building your plugin
  3. You can remove and change things based on what you need. For example, if you don't need static files in your plugin, you can delete that folder.
note

If your plugin was published on npm under your organization, your plugin's full name would be:

@{your-organization-name}/plugin-{your-plugin-name}

For example: @amplication/plugin-db-mysql

As a result, in the README.md file, you would have to change the title and the npm downloads to this:

# @{your-organization-name}/plugin-{your-plugin-name}

[![NPM Downloads](https://img.shields.io/npm/dt/@{your-organization-name}/plugin-{your-plugin-name})](https://www.npmjs.com/package/plugin-{your-plugin-name})

Keeping My Repository Up to Date with The Template

Your newly created repository is independent and does not maintain a direct link to the original template. Consequently, any changes made to the template will not automatically update your repository.

To keep your repository up to date, you can manually incorporate changes from the template repository by adding it as an additional remote and merging the updates.

Here are the steps to do that:

  1. Add the template repository as a remote:
cd <your_repository>
git remote add amplication-plugin-template https://github.com/amplication/plugin-template.git

amplication-plugin-template is the origin name. You can name it however you wan't, but make sure you change it in all commands below.

  1. Fetch changes from the template repository
git fetch amplication-plugin-template
  1. Merge or rebase the changes from the template repository
  • Merge:
git merge amplication-plugin-template/main

This will create a new merge commit in your repository that includes the changes from the template repository

  • Rebase:
git rebase amplication-plugin-template/main

This will apply the changes from the template repository on top of your local commits, effectively rewriting your local commit history.

  1. Resolve conflicts, if any - if there are conflicts between your repository and the template repository, Git will prompt you to resolve them. Edit the affected files to manually resolve the conflicts, then stage and commit the changes
  2. Push the changes to your remote repository:
    git push origin <your_default_branch_name> 
note

This process only updates your repository with the changes made in the template repository at the time of fetching. You'll need to repeat steps 2-5 whenever you want to update your repository with the latest changes from the template repository.

Testing Your Plugin

During and after the development of your new plugin, you can follow these instructions in order to test your plugin locally

Example: How we created the MySQL Plugin

The most straightforward example to illustrate this development workflow is the MySQL plugin, as we already have the functionality of a database connection:

  • We use Prisma as a ORM and Prisma supports MySQL as a provider - changing the provider on Prisma Schema.
  • We use environment variable for the Prisma schema database’s URL and for the docker-compose values - change the environment variables where needed and their values.
  • Unexpected behaviors: the Prisma MySQL provider does not support lists of primitive types

Translating the above sections to events:

register(): Events {
return {
CreateServer: {
before: this.beforeCreateServer,
},
CreateServerDotEnv: {
before: this.beforeCreateServerDotEnv,
},
CreateServerDockerCompose: {
before: this.beforeCreateServerDockerCompose,
},
CreateServerDockerComposeDB: {
before: this.beforeCreateServerDockerComposeDB,
after: this.afterCreateServerDockerComposeDB,
},
CreatePrismaSchema: {
before: this.beforeCreatePrismaSchema,
},
};
}

CreateServer : before

On this event we are taking care of the Prisma limitation regrading list of primitives values on MySQL provider.

This is also a good example for a use case where the error should be thrown from the plugin itself.

beforeCreateServer(context: DsgContext, eventParams: CreateServerParams) {
const generateErrorMessage = (
entityName: string,
fieldName: string
) => `MultiSelectOptionSet (list of primitives type) on entity: ${entityName}, field: ${fieldName}, is not supported by MySQL prisma provider.
You can select another data type or change your DB to PostgreSQL`;

context.entities?.forEach(({ name: entityName, fields }) => {
const field = fields.find(
({ dataType }) => dataType === EnumDataType.MultiSelectOptionSet
);
if (field) {
context.logger.error(generateErrorMessage(entityName, field.name));
throw new Error(generateErrorMessage(entityName, field.name));
}
});

return eventParams;
}

CreateServerDotEnv : before

On this event we send our event params which are the environment variables for the MySQL database. As a result the .env file will be generated not only with the default variables that it is already holds, but also with our environment variables.

CreateServerDockerCompose : before

On this event we send our event params which are the YAML properties and values for the MySQL database. As a result the docker-compose.yml file will be generated in a way that the PostgreSQL properties will be replaced by the MySQL properties.

CreateServerDockerComposeDB : before

This event is responsible for generating the docker-compose.db.yml file. In this file we have the docker image of the PostgreSQL database. Therefore, here we have a good example for the skip default behavior usage. We can skip this file generation and provide a different functionality, in this case - a different file, later on - in the after function.

beforeCreateServerDockerComposeDB(
context: DsgContext,
eventParams: CreateServerDockerComposeDBParams
) {
context.utils.skipDefaultBehavior = true;
return eventParams;
}

CreateServerDockerComposeDB : after

On this event, after we skip the default behavior in the before function, we provide our docker-compose.db.yml file.

async afterCreateServerDockerComposeDB(context: DsgContext) {
const staticPath = resolve(__dirname, "../static");
const staticsFiles = await context.utils.importStaticModules(
staticPath,
context.serverDirectories.baseDirectory
);

return staticsFiles;
}

CreatePrismaSchema ⇒ before

This event is responsible to manipulate this part on the Prisma schema:

export const dataSource: DataSource = {
name: "mysql",
provider: DataSourceProvider.MySQL,
url: {
name: "DB_URL",
},
};

We use the event params to:

  • change the data source name form postgres to mysql
  • change the provider from postgresql to MySQL

(the DB_URL is handled by CreateServerDotEnv)