name: joplin-publisher description: Import markdown documents and mermaid diagrams into Joplin using the Joplin CLI.
Joplin Publisher Skill
Purpose
Import markdown documents and mermaid diagrams into Joplin using the Joplin CLI.
When to Use
- Exporting research to Joplin notebooks
- Creating documentation in Joplin
- Generating diagrams for Joplin knowledge base
- Syncing analysis results to Joplin
Overview
Joplin uses a database-backed system. Content is imported via the Joplin CLI or API. This skill focuses on CLI-based imports.
Prerequisites
Install Joplin CLI
# Using npm
npm install -g joplin
# Verify installation
joplin version
Configure Joplin CLI
# Set sync target (if needed)
joplin config sync.target 2 # Filesystem
joplin config sync.2.path /path/to/sync/folder
# Or connect to Joplin Server/Cloud
joplin config sync.target 9
joplin config sync.9.path https://your-joplin-server.com
joplin config sync.9.username your-username
Joplin CLI Commands
List Notebooks
joplin ls / # List top-level notebooks
joplin ls /Notebook # List notes in notebook
Create Notebook
joplin mkbook "Notebook Name"
joplin mkbook "Parent/Child" # Nested notebook
Import Markdown
# Import single file to notebook
joplin import /path/to/file.md --notebook "Notebook Name"
# Import directory
joplin import /path/to/folder --notebook "Notebook Name"
# Import with format specification
joplin import /path/to/file.md --format md --notebook "Notebook Name"
Create Note Directly
# Create note from stdin
echo "# Title
Content" | joplin mknote "Note Title" --notebook "Notebook Name"
# Create from file content
cat file.md | joplin mknote "Note Title" --notebook "Notebook Name"
Publishing Workflow
Step 1: Validate Notebook Exists
import subprocess
def notebook_exists(notebook: str) -> bool:
"""Check if a Joplin notebook exists."""
result = subprocess.run(
["joplin", "ls", "/"],
capture_output=True,
text=True
)
notebooks = result.stdout.strip().split('
')
return notebook in notebooks
def create_notebook_if_missing(notebook: str):
"""Create notebook if it doesn't exist."""
if not notebook_exists(notebook):
subprocess.run(
["joplin", "mkbook", notebook],
check=True
)
Step 2: Prepare Content
def prepare_markdown(
title: str,
content: str,
tags: list = None
) -> str:
"""
Prepare markdown content for Joplin import.
Joplin supports YAML frontmatter for metadata.
"""
lines = [f"# {title}", ""]
if tags:
lines.extend([
"---",
f"tags: {', '.join(tags)}",
"---",
""
])
lines.append(content)
return '
'.join(lines)
Step 3: Write Temporary File
import tempfile
from pathlib import Path
def write_temp_markdown(content: str, filename: str) -> Path:
"""Write content to a temporary markdown file."""
temp_dir = Path(tempfile.mkdtemp())
file_path = temp_dir / f"{filename}.md"
file_path.write_text(content, encoding='utf-8')
return file_path
Step 4: Import to Joplin
def import_to_joplin(
file_path: Path,
notebook: str
) -> bool:
"""Import markdown file to Joplin notebook."""
result = subprocess.run(
[
"joplin", "import",
str(file_path),
"--notebook", notebook
],
capture_output=True,
text=True
)
if result.returncode != 0:
raise JoplinImportError(f"Import failed: {result.stderr}")
return True
Complete Publishing Function
def publish_to_joplin(
notebook: str,
title: str,
content: str,
tags: list = None
) -> bool:
"""
Publish markdown content to a Joplin notebook.
Args:
notebook: Target notebook name
title: Note title
content: Markdown content (can include mermaid)
tags: Optional list of tags
Returns:
True if successful
"""
# Ensure notebook exists
create_notebook_if_missing(notebook)
# Prepare content
full_content = prepare_markdown(title, content, tags)
# Write to temp file
temp_file = write_temp_markdown(full_content, title)
try:
# Import to Joplin
import_to_joplin(temp_file, notebook)
return True
finally:
# Cleanup
temp_file.unlink()
temp_file.parent.rmdir()
Document Formats
Basic Note
# Note Title
Content goes here.
## Section
More content.
Note with Tags
# Note Title
Content here.
Note with Mermaid
Joplin supports mermaid diagrams natively in markdown:
# System Architecture
```mermaid
flowchart TD
A[Client] --> B[Server]
B --> C[(Database)]
Description
The system consists of...
## Notebook Organization
### Flat Structure
Notebooks/ ├── Research ├── Projects ├── Meetings └── Archive
### Nested Structure
Notebooks/ ├── Work/ │ ├── Project Alpha │ └── Project Beta ├── Personal/ │ ├── Notes │ └── Ideas └── Archive/
Create nested notebooks:
```bash
joplin mkbook "Work"
joplin mkbook "Work/Project Alpha"
Usage Examples
Publish Research Note
publish_to_joplin(
notebook="Research",
title="API Design Patterns",
content="""
## Overview
Key findings from API design research.
## REST Best Practices
1. Use nouns for resources
2. Use HTTP methods correctly
3. Version your API
## GraphQL Considerations
- Schema-first design
- Query optimization
""",
tags=["api", "research", "design"]
)
Publish Diagram
publish_to_joplin(
notebook="Architecture",
title="System Overview Diagram",
content="""
## Architecture Diagram
```mermaid
flowchart TB
subgraph Frontend
A[Web App]
B[Mobile App]
end
subgraph Backend
C[API Server]
D[Worker]
end
subgraph Data
E[(PostgreSQL)]
F[(Redis)]
end
A --> C
B --> C
C --> E
C --> F
D --> E
Components
| Component | Technology | Purpose |
|---|---|---|
| Web App | React | User interface |
| API Server | FastAPI | REST API |
| Worker | Celery | Background jobs |
| """, |
tags=["architecture", "diagram"]
)
### Publish Meeting Notes
```python
publish_to_joplin(
notebook="Meetings/2024",
title="Project Sync - Jan 15",
content="""
## Attendees
- Alice
- Bob
- Charlie
## Agenda
1. Sprint review
2. Blockers
3. Next steps
## Notes
### Sprint Review
- Feature X completed
- Bug Y in progress
### Blockers
- Waiting on API access
### Action Items
- [ ] Alice: Follow up on API access
- [ ] Bob: Complete bug fix
- [ ] Charlie: Update documentation
""",
tags=["meeting", "project-alpha"]
)
Batch Import
For importing multiple documents:
def batch_import(
notebook: str,
documents: list[dict]
) -> dict:
"""
Import multiple documents to Joplin.
Args:
notebook: Target notebook
documents: List of {title, content, tags} dicts
Returns:
{success: int, failed: int, errors: list}
"""
results = {"success": 0, "failed": 0, "errors": []}
for doc in documents:
try:
publish_to_joplin(
notebook=notebook,
title=doc["title"],
content=doc["content"],
tags=doc.get("tags", [])
)
results["success"] += 1
except Exception as e:
results["failed"] += 1
results["errors"].append({
"title": doc["title"],
"error": str(e)
})
return results
Error Handling
class JoplinError(Exception):
"""Base exception for Joplin operations."""
pass
class JoplinNotInstalledError(JoplinError):
"""Joplin CLI not found."""
pass
class JoplinImportError(JoplinError):
"""Failed to import content."""
pass
class NotebookNotFoundError(JoplinError):
"""Notebook does not exist."""
pass
def check_joplin_installed():
"""Verify Joplin CLI is available."""
result = subprocess.run(
["joplin", "version"],
capture_output=True
)
if result.returncode != 0:
raise JoplinNotInstalledError(
"Joplin CLI not found. Install with: npm install -g joplin"
)
Joplin API Alternative
For more control, use the Joplin Data API:
import requests
class JoplinAPI:
def __init__(self, token: str, port: int = 41184):
self.base_url = f"http://localhost:{port}"
self.token = token
def create_note(
self,
title: str,
body: str,
parent_id: str = None
) -> dict:
"""Create a note via Joplin API."""
response = requests.post(
f"{self.base_url}/notes",
params={"token": self.token},
json={
"title": title,
"body": body,
"parent_id": parent_id
}
)
response.raise_for_status()
return response.json()
Enable the API in Joplin Desktop: Options → Web Clipper → Enable
Checklist
Before publishing:
- Joplin CLI is installed and configured
- Target notebook exists or will be created
- Content is valid markdown
- Mermaid diagrams use correct syntax
- Tags are properly formatted
- Sync is configured (if using cloud/server)