diff --git a/README.md b/README.md
index 384cb54b65..5c357471bf 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,33 @@ Backstage is an open platform for building developer portals.
## Getting started
+## Plugins
+
+### Creating a Plugin
+
+Run the following:
+
+```bash
+$ ./tools/cookiecutter/init.sh frontend/packages/plugins/_template --output-dir frontend/packages/plugins
+```
+
+This will generate a plugin in the `frontend/packages/plugins` folder. It is important to note you will still need to include the plugin in your `frontend/packages/app` in the `package.json`. You will then be able to import it as follows:
+
+```bash
+$ ./tools/cookiecutter/init.sh frontend/packages/plugins/_template --output-dir frontend/packages/plugins
+plugin_name [example-plugin]: github-api
+
+$ vim frontend/packages/app/package.json
+# Add the following line to your package.json
+# Note all plugins are prefixed with "plugin-" by default with a version number of "0.0.0"
+# "@backstage/plugin-github-api": "0.0.0",
+
+$ vim frontend/packages/app/src/App.tsx
+# Add the following line to import your generated component
+# import { ExampleComponent } from '@backstage/plugin-github-api';
+#
+```
+
## Documentation
## License
diff --git a/frontend/packages/plugins/_template/cookiecutter.json b/frontend/packages/plugins/_template/cookiecutter.json
new file mode 100644
index 0000000000..2cc21e895e
--- /dev/null
+++ b/frontend/packages/plugins/_template/cookiecutter.json
@@ -0,0 +1,3 @@
+{
+ "plugin_name": "example-plugin"
+}
diff --git a/frontend/packages/plugins/_template/hooks/pre_gen_project.py b/frontend/packages/plugins/_template/hooks/pre_gen_project.py
new file mode 100644
index 0000000000..04f63eb219
--- /dev/null
+++ b/frontend/packages/plugins/_template/hooks/pre_gen_project.py
@@ -0,0 +1,14 @@
+import re
+import sys
+
+
+def validate(name, regex, error_message):
+ if not re.match(regex, name):
+ print(error_message % name)
+
+ # exits with status 1 to indicate failure
+ sys.exit(1)
+
+
+validate('{{ cookiecutter.plugin_name }}',
+ r'^[a-z-]+$', 'ERROR: %s is not a valid plugin name! Only lowercase letters and hyphens are valid (Examples: my-amazing-plugin, myamazingplugin).')
diff --git a/frontend/packages/plugins/_template/src/index.ts b/frontend/packages/plugins/_template/src/index.ts
deleted file mode 100644
index b68aea57f9..0000000000
--- a/frontend/packages/plugins/_template/src/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './plugin';
diff --git a/frontend/packages/plugins/_template/src/plugin.test.ts b/frontend/packages/plugins/_template/src/plugin.test.ts
deleted file mode 100644
index eda9eeb72a..0000000000
--- a/frontend/packages/plugins/_template/src/plugin.test.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import plugin from './plugin';
-
-describe('plugin-1', () => {
- it('should export plugin', () => {
- expect(plugin.id).toBe('change-me-id');
- });
-});
diff --git a/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/README.md b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/README.md
new file mode 100644
index 0000000000..86e39457da
--- /dev/null
+++ b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/README.md
@@ -0,0 +1 @@
+Welcome to your {{cookiecutter.plugin_name}} plugin!
diff --git a/frontend/packages/plugins/_template/jest.config.js b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/jest.config.js
similarity index 100%
rename from frontend/packages/plugins/_template/jest.config.js
rename to frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/jest.config.js
diff --git a/frontend/packages/plugins/_template/jest.setup.ts b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/jest.setup.ts
similarity index 100%
rename from frontend/packages/plugins/_template/jest.setup.ts
rename to frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/jest.setup.ts
diff --git a/frontend/packages/plugins/_template/package.json b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/package.json
similarity index 91%
rename from frontend/packages/plugins/_template/package.json
rename to frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/package.json
index 1039d0a49b..85f56f7fa0 100644
--- a/frontend/packages/plugins/_template/package.json
+++ b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/package.json
@@ -1,5 +1,5 @@
{
- "name": "@backstage/__CHANGE_ME__",
+ "name": "@backstage/plugin-{{ cookiecutter.plugin_name }}",
"version": "0.0.0",
"main": "src/index.ts",
"main:src": "src/index.ts",
diff --git a/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/components/ExampleComponent/ExampleComponent.test.tsx b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/components/ExampleComponent/ExampleComponent.test.tsx
new file mode 100644
index 0000000000..c231eea700
--- /dev/null
+++ b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/components/ExampleComponent/ExampleComponent.test.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import ExampleComponent from './ExampleComponent';
+
+describe('ExampleComponent', () => {
+ it('should render', () => {
+ const rendered = render();
+ expect(rendered.getByText('Hello!')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/components/ExampleComponent/ExampleComponent.tsx b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/components/ExampleComponent/ExampleComponent.tsx
new file mode 100644
index 0000000000..bd7c9235ae
--- /dev/null
+++ b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/components/ExampleComponent/ExampleComponent.tsx
@@ -0,0 +1,16 @@
+import React, { FC } from 'react';
+import Button from '@material-ui/core/Button';
+
+const ExampleComponent: FC<{}> = () => {
+ return (
+
+ );
+};
+
+export default ExampleComponent;
diff --git a/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/components/ExampleComponent/index.ts b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/components/ExampleComponent/index.ts
new file mode 100644
index 0000000000..520a3bf553
--- /dev/null
+++ b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/components/ExampleComponent/index.ts
@@ -0,0 +1 @@
+export { default } from './ExampleComponent';
diff --git a/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/index.ts b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/index.ts
new file mode 100644
index 0000000000..a2e45f3a79
--- /dev/null
+++ b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/index.ts
@@ -0,0 +1,2 @@
+export { default } from './plugin';
+export { default as ExampleComponent } from './components/ExampleComponent';
diff --git a/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/plugin.test.ts b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/plugin.test.ts
new file mode 100644
index 0000000000..869fa6f67f
--- /dev/null
+++ b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/plugin.test.ts
@@ -0,0 +1,7 @@
+import plugin from './plugin';
+
+describe('{{ cookiecutter.plugin_name }}', () => {
+ it('should export plugin', () => {
+ expect(plugin.id).toBe('{{ cookiecutter.plugin_name }}');
+ });
+});
diff --git a/frontend/packages/plugins/_template/src/plugin.ts b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/plugin.ts
similarity index 67%
rename from frontend/packages/plugins/_template/src/plugin.ts
rename to frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/plugin.ts
index a20c7ccd89..0424b5d999 100644
--- a/frontend/packages/plugins/_template/src/plugin.ts
+++ b/frontend/packages/plugins/_template/{{ cookiecutter.plugin_name }}/src/plugin.ts
@@ -1,5 +1,5 @@
import { createPlugin } from '@backstage/core';
export default createPlugin({
- id: 'change-me-id',
+ id: '{{ cookiecutter.plugin_name }}',
});
diff --git a/tools/cookiecutter/Dockerfile b/tools/cookiecutter/Dockerfile
new file mode 100644
index 0000000000..9affb40d0f
--- /dev/null
+++ b/tools/cookiecutter/Dockerfile
@@ -0,0 +1,3 @@
+FROM python:3.8-slim
+RUN pip install cookiecutter==1.7.0 --index-url https://pypi.python.org/simple
+ENTRYPOINT [ "cookiecutter" ]
\ No newline at end of file
diff --git a/tools/cookiecutter/init.sh b/tools/cookiecutter/init.sh
new file mode 100755
index 0000000000..717ab50cd3
--- /dev/null
+++ b/tools/cookiecutter/init.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+set -e
+
+# Build the container
+docker build $(pwd)/tools/cookiecutter -t backstage/tools/cookiecutter --quiet
+
+# Run cookiecutter with our arguments
+COOKIECUTTER_FLAGS=${@:-""}
+docker run -it -v $(pwd):/app -w /app backstage/tools/cookiecutter $COOKIECUTTER_FLAGS