Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions .github/ISSUE_TEMPLATE/add-plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: Add a Plugin
description: Submit a new plugin to the Docusaurus Community Plugin Directory.
title: "[Plugin] "
labels:
- plugin-submission
body:
- type: markdown
attributes:
value: |
Fill in the fields below. A bot will generate your YAML file and open a draft PR automatically.
Review the [contributing guide](https://docusaurus.community/contributing/plugins) before submitting.

- type: input
id: name
attributes:
label: Plugin name
description: Display name shown in the directory.
placeholder: "My Awesome Plugin"
validations:
required: true

- type: input
id: id
attributes:
label: Plugin ID
description: "`author.plugin-short-name` — must be unique. Lowercase letters, numbers, and hyphens only."
placeholder: "yourname.plugin-short-name"
validations:
required: true

- type: textarea
id: description
attributes:
label: Description
description: One sentence describing what the plugin does.
placeholder: "A plugin that does X."
validations:
required: true

- type: input
id: website
attributes:
label: Website / Repository URL
description: Public website or repository URL.
placeholder: "https://github.com/yourname/your-plugin"
validations:
required: true

- type: input
id: source
attributes:
label: Source code URL
description: Source code URL. Required for open-source plugins — leave blank for closed-source.
placeholder: "https://github.com/yourname/your-plugin"

- type: input
id: author
attributes:
label: Author
description: Your GitHub username or organisation name.
placeholder: "yourname"

- type: input
id: preview
attributes:
label: Preview image URL
description: URL to a preview image. Leave blank to auto-generate.
placeholder: "https://..."

- type: dropdown
id: status
attributes:
label: Status
description: Current maintenance status of the plugin.
options:
- maintained
- unmaintained
- unknown
validations:
required: true

- type: input
id: minimumVersion
attributes:
label: Minimum Docusaurus version
description: e.g. `3.0.0`. Leave blank if not applicable.
placeholder: "3.0.0"

- type: checkboxes
id: tags
attributes:
label: Tags
description: Select all that apply.
options:
- label: search
- label: api
- label: utility
- label: content
- label: theme
- label: markdown
- label: analytics
- label: integration
- label: seo
- label: editing
- label: docusaurus

- type: textarea
id: npmPackages
attributes:
label: npm package name(s)
description: One package name per line.
placeholder: "your-npm-package-name"

- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: The plugin works with a current or recent stable release of Docusaurus.
required: true
- label: I have read the [contributing guide](https://docusaurus.community/contributing/plugins).
required: true
197 changes: 197 additions & 0 deletions .github/workflows/create-plugin-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
name: Create Plugin PR from Issue

on:
issues:
types: [opened]

permissions:
contents: write
pull-requests: write
issues: write

jobs:
create-pr:
if: contains(github.event.issue.labels.*.name, 'plugin-submission')
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const body = context.payload.issue.body ?? '';
const issueNumber = context.payload.issue.number;
const issueUrl = context.payload.issue.html_url;

function parseField(body, heading) {
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`### ${escaped}\\r?\\n\\r?\\n([\\s\\S]*?)(?=\\r?\\n### |$)`);
const match = body.match(regex);
if (!match) return null;
const val = match[1].trim();
return val === '_No response_' || val === '' ? null : val;
}

function parseCheckboxes(body, heading) {
const section = parseField(body, heading);
if (!section) return [];
return section.split('\n')
.filter(line => /^- \[x\]/i.test(line.trim()))
.map(line => line.replace(/^- \[x\] /i, '').trim());
}

const name = parseField(body, 'Plugin name');
const id = parseField(body, 'Plugin ID');
const description = parseField(body, 'Description');
const website = parseField(body, 'Website / Repository URL');
const source = parseField(body, 'Source code URL');
const author = parseField(body, 'Author');
const preview = parseField(body, 'Preview image URL');
const status = parseField(body, 'Status');
const minimumVersion = parseField(body, 'Minimum Docusaurus version');
const npmPackagesRaw = parseField(body, 'npm package name(s)');
const tags = parseCheckboxes(body, 'Tags');

const errors = [];
if (!name) errors.push('Plugin name is required');
if (!id) errors.push('Plugin ID is required');
if (id && !/^[a-z0-9-]+\.[a-z0-9-]+$/.test(id)) errors.push('Plugin ID must be in `author.plugin-name` format using lowercase letters, numbers, and hyphens only');
if (!description) errors.push('Description is required');
if (!website) errors.push('Website / Repository URL is required');
if (!status) errors.push('Status is required');

if (errors.length > 0) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: [
'Thank you for your plugin submission! Unfortunately some required fields are missing or invalid:',
'',
...errors.map(e => `- ${e}`),
'',
'Please close this issue and open a new one with all fields correctly filled in.',
].join('\n'),
});
return;
}

const npmPackages = npmPackagesRaw
? npmPackagesRaw.split('\n').map(l => l.trim()).filter(Boolean)
: [];

const filename = `${id}.yaml`;
const branch = `plugin/${id}`;

// Check if the file already exists
try {
await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: `data/plugins/${filename}`,
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: `A plugin with ID \`${id}\` already exists in the directory. Please choose a unique ID, then close this issue and open a new one.`,
});
return;
} catch (e) {
// File doesn't exist — expected
}

// Generate YAML
const yamlLines = [
'# yaml-language-server: $schema=https://docusaurus.community/schema/plugin/1.0.0.json',
`id: ${id}`,
`name: "${name.replace(/"/g, '\\"')}"`,
`description: "${description.replace(/"/g, '\\"')}"`,
`preview: ${preview || 'null'}`,
`website: ${website}`,
`source: ${source || 'null'}`,
`author: ${author || 'null'}`,
];

if (tags.length > 0) {
yamlLines.push('tags:');
tags.forEach(t => yamlLines.push(` - ${t}`));
} else {
yamlLines.push('tags: []');
}

yamlLines.push(`minimumVersion: ${minimumVersion || 'null'}`);
yamlLines.push(`status: ${status}`);

if (npmPackages.length > 0) {
yamlLines.push('npmPackages:');
npmPackages.forEach(p => yamlLines.push(` - ${p}`));
} else {
yamlLines.push('npmPackages: []');
}

const yaml = yamlLines.join('\n') + '\n';

// Get main branch SHA
const mainRef = await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'heads/main',
});
const baseSha = mainRef.data.object.sha;

// Create branch, falling back to a unique name if it already exists
let actualBranch = branch;
try {
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/heads/${branch}`,
sha: baseSha,
});
} catch (e) {
actualBranch = `${branch}-${issueNumber}`;
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/heads/${actualBranch}`,
sha: baseSha,
});
}

// Commit the YAML file
await github.rest.repos.createOrUpdateFileContents({
owner: context.repo.owner,
repo: context.repo.repo,
path: `data/plugins/${filename}`,
message: `chore: add plugin ${id} from issue #${issueNumber}`,
content: Buffer.from(yaml).toString('base64'),
branch: actualBranch,
});

// Open a draft PR
const pr = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Add plugin: ${name}`,
head: actualBranch,
base: 'main',
body: [
`Adds \`${id}\` to the plugin directory.`,
'',
`Generated automatically from issue ${issueUrl}.`,
'',
`Closes #${issueNumber}`,
].join('\n'),
draft: true,
});

// Comment on the issue with the PR link
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: [
`Thank you for your plugin submission! A draft PR has been created: ${pr.data.html_url}`,
'',
'A maintainer will review it shortly. Please check the PR for any CI validation errors.',
].join('\n'),
});
Binary file modified .yarn/install-state.gz
Binary file not shown.
11 changes: 9 additions & 2 deletions contributing/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ The plugin directory is powered by [`@homotechsual/docusaurus-plugin-showcase`](

## Before you submit

- The plugin must be **open source** with a publicly accessible source repository.
- The plugin must work with a current or recent stable release of Docusaurus.
- If the plugin is **open source** the source repository must be publicly accessible and present in the `source` field of your YAML file.
- If the plugin is **closed source** the source repository must not be present in the `source` field of your YAML file.
- The plugin must work with a current or recent stable release of Docusaurus to have `status` set to `maintained`. If the plugin is no longer maintained, set `status` to `unmaintained`. If you are unsure, set `status` to `unknown`.
- One entry per npm package (or logical plugin unit).

## Create your YAML file
Expand Down Expand Up @@ -55,6 +56,12 @@ npmPackages:

## Submit a pull request

### Option A — automated (recommended)

Open a [plugin submission issue](https://github.com/DocusaurusCommunity/website/issues/new?template=add-plugin.yml) and fill in the form. A bot will generate your YAML file, open a draft PR, and comment on the issue with a link. You do not need to fork the repository.

### Option B — manual

1. Fork [`DocusaurusCommunity/website`](https://github.com/DocusaurusCommunity/website) on GitHub.
2. Add your YAML file to `data/plugins/`.
3. Open a pull request against the `main` branch.
Expand Down
Loading