Extension Submission Guide
This guide explains how to submit extensions to the Vault Copilot marketplace.
Overview
Vault Copilot includes an in-app submission wizard (desktop only) that automates creating a pull request to the official obsidian-vault-copilot repository.
Under the hood, the submission workflow is implemented by GitHubSubmissionService, which shells out to the GitHub CLI (gh) and git to:
- Validate your extension folder
- Create or reuse your fork
- Create a branch
- Copy your extension files into the repo under
extensions/... - Commit, push, and open a pull request
Prerequisites
- GitHub CLI (
gh): Must be installed and authenticatedgh auth login -
Git: Must be installed and available on your PATH.
-
Desktop + local vault: The in-app submission wizard requires a local vault on desktop (it needs filesystem access and runs
gh/git). - Extension files:
- The type-specific extension file is required (examples:
my-agent.agent.md,my-prompt.prompt.md,skill.md,mcp-config.json). manifest.jsonandREADME.mdare required for validation.
- If you use the in-app wizard, it can generate
manifest.jsonandREADME.mdin a temporary workspace if they’re missing.
- The type-specific extension file is required (examples:
- Submitter tracking (recommended): Add
submittedByto yourmanifest.jsonto lock down who can publish future updates.- If you want this field set, create your own
manifest.jsonin the extension folder before submitting (the wizard won’t overwrite an existing manifest). - See: Extension update validation
- If you want this field set, create your own
In-app submission (recommended)
In Obsidian (desktop), run the command:
- Submit Extension to Catalog
The wizard guides you through selecting an extension folder, optionally generating/cleaning up content, and then opening a PR.
What the wizard does
- Prepares a temporary working folder (so your vault isn’t mutated during submission)
- Ensures
manifest.json,README.md, and preview assets exist in the working folder - Runs the GitHub workflow:
- Fork (if needed)
- Create a branch
- Copy your extension under
extensions/<type-folder>/<id>/ - Commit + push
- Create the PR
Notes:
- The wizard is desktop-only.
- It runs
gh/gitlocally and uses a temporary folder under your OS temp directory.
Programmatic / headless usage (advanced)
If you’re automating submissions outside Obsidian, you can call GitHubSubmissionService directly.
Workflow Steps
The submission service executes the following workflow:
- Validation - Validates extension files and manifest
- GitHub Setup - Checks CLI authentication and fork existence
- Branch Creation - Creates a new branch in your fork
- File Copy - Copies extension files to the appropriate directory
- Commit & Push - Commits and pushes changes to your fork
- Pull Request - Creates a PR to the upstream repository
Usage Example
Basic Submission
import { GitHubSubmissionService } from "../src/extensions/GitHubSubmissionService";
// Initialize the service
const service = new GitHubSubmissionService({
upstreamOwner: "danielshue",
upstreamRepo: "vault-copilot-extensions",
targetBranch: "main"
});
await service.initialize();
// Submit your extension
const result = await service.submitExtension({
extensionPath: "/path/to/my-agent",
extensionId: "my-agent",
extensionType: "agent",
version: "1.0.0",
branchName: "add-my-agent-v1.0.0"
});
// Check the result
if (result.success) {
console.log(`✅ PR created: ${result.pullRequestUrl}`);
console.log(`PR #${result.pullRequestNumber}`);
} else {
console.error("❌ Submission failed:", result.error);
console.error("Validation errors:", result.validationErrors);
}
// Clean up
await service.cleanup();
With Custom Messages
const result = await service.submitExtension({
extensionPath: "/vault/Reference/Agents/daily-journal",
extensionId: "daily-journal",
extensionType: "agent",
version: "2.0.0",
branchName: "update-daily-journal-2.0.0",
// Custom commit message
commitMessage: "Update Daily Journal Agent to v2.0.0 with new features",
// Custom PR title
prTitle: "[Agent] Daily Journal v2.0.0 - Enhanced journaling features",
// Custom PR description
prDescription: `
## Extension Update
**Extension:** Daily Journal Agent
**Version:** 2.0.0
**Type:** agent
### Changes
- Added mood tracking
- Improved template generation
- Enhanced date handling
### Testing
Tested on Obsidian 1.5.0 with Vault Copilot 0.0.18
### Checklist
- [x] All files validated
- [x] No breaking changes
- [x] Documentation updated
`
});
Validation Only
You can validate your extension without submitting:
const validation = await service.validateExtension({
extensionPath: "/path/to/my-extension",
extensionId: "my-extension",
extensionType: "agent",
version: "1.0.0",
branchName: "test-branch"
});
if (!validation.valid) {
console.error("Validation errors:");
validation.errors.forEach(error => console.error(` - ${error}`));
}
if (validation.warnings.length > 0) {
console.warn("Validation warnings:");
validation.warnings.forEach(warning => console.warn(` - ${warning}`));
}
Extension Structure
Directory Layout
Your extension directory should follow this structure:
my-agent/
├── manifest.json # Required
├── README.md # Required
├── my-agent.agent.md # Extension file (type-specific)
└── preview.png # Optional (recommended)
manifest.json Example
{
"id": "my-agent",
"name": "My Agent",
"version": "1.0.0",
"type": "agent",
"submittedBy": "github-username",
"description": "A helpful agent for task automation",
"author": {
"name": "Your Name",
"url": "https://github.com/yourusername"
},
"minVaultCopilotVersion": "0.0.1",
"categories": ["Productivity", "Utility"],
"tags": ["automation", "tasks"],
"files": [
{
"source": "my-agent.agent.md",
"installPath": "extensions/agents/my-agent/my-agent.agent.md"
}
],
"repository": "https://github.com/yourusername/my-extension-repo",
"tools": ["create_note", "read_note", "search_vault"]
}
Validate Locally
Validate before submitting a PR:
npm install
# Validate a specific extension folder
npm run validate:extension -- extensions/agents/my-agent
# Or run the validator directly
node scripts/validate-extension.cjs extensions/agents/my-agent
Tip: The validator is primarily designed to run from a clone of this repository (so it can check for duplicates and apply catalog rules).
Preview the Docs Site Locally
If you update docs (including your extension README.md), you can preview the Jekyll site before publishing:
bundle install
bundle exec jekyll serve --livereload --incremental
Then open:
http://127.0.0.1:4000/obsidian-vault-copilot/
Note: The site uses Highlight.js for client-side code syntax highlighting.
Validation Rules
The service validates:
- Required Files
manifest.jsonmust exist and be valid JSONREADME.mdmust exist- Extension file must match the expected pattern for the type
- Manifest Fields
idmust match the directory namenameis requiredversionmust follow semantic versioning (x.y.z)typemust match the submission type -descriptionshould be 200 characters or fewer (warnings/errors may be raised by tooling)
- File Sizes
- Individual files should be under 500KB
- Total extension size must be under 2MB
- Warning if files exceed 100KB
- Extension Types
- agent: Expects
{id}.agent.md - voice-agent: Expects
{id}.voice-agent.md - prompt: Expects
{id}.prompt.md - skill: Expects
skill.md - mcp-server: Expects
mcp-config.json - automation: Expects
automation-config.json
- agent: Expects
Error Handling
The service provides detailed error information:
const result = await service.submitExtension(params);
if (!result.success) {
if (result.validationErrors.length > 0) {
// Validation failed
console.error("Fix these validation errors:");
result.validationErrors.forEach(err => console.error(err));
} else if (result.error) {
// Submission error
console.error("Submission error:", result.error);
// Additional details available
if (result.details) {
console.error("Details:", result.details);
}
}
}
GitHub Operations (what runs on your machine)
The submission flow uses the real GitHub CLI (gh) and git.
At a high level, it performs operations equivalent to:
gh auth statusto verify authenticationgh repo fork(only if you’re not the upstream owner and don’t already have a fork)git init+git fetch+ sparse checkout to retrieve onlyextensions/git checkout -b <branch>git add ./git commitgit push -u origin <branch>gh pr createagainstdanielshue/obsidian-vault-copilot
Best Practices
- Always validate first
const validation = await service.validateExtension(params); if (!validation.valid) return; - Use descriptive branch names
branchName: `add-${extensionId}-v${version}` - Clean up after use
try { await service.initialize(); const result = await service.submitExtension(params); } finally { await service.cleanup(); } - Handle errors gracefully
if (!result.success) { // Show user-friendly error message // Log details for debugging }
Testing
Run the test suite:
npm test
# Once dedicated tests are added for GitHubSubmissionService, you can run:
# npm test -- src/tests/extensions/GitHubSubmissionService.test.ts
Troubleshooting
“GitHub CLI not authenticated”
Solution: Run gh auth login and authenticate with GitHub
“Validation failed: manifest.json is not valid JSON”
Solution: Validate your JSON using a linter or jsonlint
“Extension file not found”
Solution: Ensure the extension file matches the expected pattern for your type
“Total extension size exceeds 2MB limit”
Solution: Reduce file sizes or split into multiple extensions
Related Documentation
- Developers
- Extension authoring guide
- Extension update validation
- Marketplace technical design
- GitHub CLI documentation
API Reference
See the GitHubSubmissionService API documentation for complete type definitions and method signatures.