Overview
Community plugins extend Inkdown’s functionality and can be published for other users to install. This guide covers best practices for publishing plugins.
Plugin Structure
A publishable plugin should have this structure:
my-plugin/
├── manifest.json
├── package.json
├── tsconfig.json
├── README.md
├── LICENSE
├── src/
│ └── index.ts
├── dist/
│ └── main.js
└── .gitignore
Required Files
manifest.json
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"minAppVersion": "1.0.0",
"description": "A helpful plugin for Inkdown",
"author": "Your Name",
"authorUrl": "https://github.com/yourname"
}
package.json
{
"name": "inkdown-plugin-my-plugin",
"version": "1.0.0",
"description": "My Inkdown plugin",
"main": "dist/main.js",
"scripts": {
"build": "esbuild src/index.ts --bundle --outfile=dist/main.js --format=cjs --external:@inkdown/api",
"dev": "npm run build -- --watch",
"lint": "eslint src --ext ts",
"typecheck": "tsc --noEmit"
},
"keywords": [
"inkdown",
"plugin"
],
"author": "Your Name",
"license": "MIT",
"devDependencies": {
"@inkdown/api": "latest",
"@types/node": "^18.0.0",
"esbuild": "^0.19.0",
"typescript": "^5.0.0"
}
}
README.md
Provide clear documentation:
# My Plugin
A helpful plugin for Inkdown.
## Features
- Feature 1
- Feature 2
- Feature 3
## Installation
### From Inkdown
1. Open Settings → Plugins
2. Search for "My Plugin"
3. Click Install
### Manual Installation
1. Download the latest release from [GitHub](https://github.com/yourname/inkdown-plugin-my-plugin/releases)
2. Extract to `~/.config/inkdown/plugins/my-plugin`
3. Restart Inkdown
4. Enable the plugin in Settings → Plugins
## Usage
Describe how to use your plugin...
## Settings
Describe available settings...
## Commands
List available commands:
- **My Command** (`Mod+K`): Does something useful
## Support
For issues and feature requests, please visit the [GitHub repository](https://github.com/yourname/inkdown-plugin-my-plugin).
## License
MIT
LICENSE
Include a license file (MIT is recommended):
MIT License
Copyright (c) 2024 Your Name
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Build Process
esbuild Configuration
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
outfile: 'dist/main.js',
format: 'cjs',
external: ['@inkdown/api', '@inkdown/core'],
platform: 'node',
target: 'node18',
sourcemap: false,
minify: true,
}).catch(() => process.exit(1));
TypeScript Configuration
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"lib": ["ES2020", "DOM"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": false,
"outDir": "dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
.gitignore
node_modules/
dist/
*.log
.DS_Store
Publishing Process
1. Prepare Release
- Update version in both
manifest.json and package.json
- Build the plugin:
npm run build
- Test thoroughly in Inkdown
- Update CHANGELOG.md with changes
- Commit changes:
git commit -am "Release v1.0.0"
2. Create GitHub Release
-
Tag the release:
git tag v1.0.0
git push origin v1.0.0
-
Create release on GitHub with:
- Release title:
v1.0.0
- Release notes describing changes
- Attach
manifest.json and main.js as assets
3. Submit to Plugin Directory
Check the Inkdown documentation for the current submission process.
Typically involves:
- Fork the Inkdown plugins repository
- Add your plugin to the directory
- Submit a pull request
Versioning
Follow Semantic Versioning:
- MAJOR (1.0.0 → 2.0.0): Breaking changes
- MINOR (1.0.0 → 1.1.0): New features (backward compatible)
- PATCH (1.0.0 → 1.0.1): Bug fixes
Version Update Checklist
Best Practices
Plugin Quality
-
Performance
- Avoid blocking the main thread
- Use debouncing for frequent operations
- Clean up resources in
onunload()
-
Error Handling
- Handle all errors gracefully
- Provide useful error messages
- Log errors to console
-
User Experience
- Provide clear command names
- Show feedback with notices
- Include helpful descriptions
-
Settings
- Provide sensible defaults
- Include setting descriptions
- Validate user input
Code Quality
-
TypeScript
- Use strict mode
- Type all functions
- Avoid
any types
-
Testing
- Test on multiple platforms
- Test with different workspace configurations
- Test edge cases
-
Documentation
- Document all public APIs
- Provide usage examples
- Keep README up to date
Security
-
Input Validation
- Validate all user input
- Sanitize file paths
- Check permissions
-
Dependencies
- Minimize dependencies
- Keep dependencies updated
- Audit for vulnerabilities
-
Data Handling
- Don’t store sensitive data
- Respect user privacy
- Encrypt if necessary
Common Pitfalls
Don’t Block the UI
// ❌ Bad - blocks UI
for (const file of files) {
await this.processFile(file);
}
// ✅ Good - allows UI updates
for (const file of files) {
await this.processFile(file);
await new Promise(resolve => setTimeout(resolve, 0));
}
Clean Up Resources
// ❌ Bad - resources leak
export default class MyPlugin extends Plugin {
async onload() {
setInterval(() => this.update(), 1000);
}
}
// ✅ Good - proper cleanup
export default class MyPlugin extends Plugin {
async onload() {
this.registerInterval(
window.setInterval(() => this.update(), 1000)
);
}
}
Handle Missing Data
// ❌ Bad - assumes data exists
const title = cache.frontmatter.title;
// ✅ Good - defensive programming
const title = cache?.frontmatter?.title || 'Untitled';
// ❌ Bad - no validation
async createFile(name: string) {
await this.app.workspace.create(`${name}.md`);
}
// ✅ Good - validate input
async createFile(name: string) {
if (!name || name.trim().length === 0) {
throw new Error('File name cannot be empty');
}
const sanitized = name.replace(/[<>:"|?*]/g, '');
await this.app.workspace.create(`${sanitized}.md`);
}
Support and Maintenance
Issue Tracking
- Use GitHub Issues for bug reports
- Label issues appropriately
- Respond promptly to user questions
- Close fixed issues with release notes
Updates
- Monitor Inkdown API changes
- Update
minAppVersion when needed
- Maintain backward compatibility
- Deprecate features gradually
- Be responsive to feedback
- Accept contributions
- Credit contributors
- Maintain a positive community
Resources
Example Plugins
Study these built-in plugins for examples:
- Word Count: Status bar integration
- Quick Finder: Fuzzy search modal
- Slash Commands: Editor suggest
- Emoji: Markdown processing + editor extensions