✓ Verified 💻 Development ✓ Enhanced Data

Ghost Admin

Ghost CMS content management via Admin API v5.x.

Rating
4.4 (314 reviews)
Downloads
9,719 downloads
Version
1.0.0

Overview

Ghost CMS content management via Admin API v5.x.

Complete Documentation

View Source →

Ghost Skill

Full Ghost Admin API v5 client. HS256 JWT auth and all HTTP calls via stdlib (urllib) - zero external dependencies. Credentials: ~/.openclaw/secrets/ghost_creds · Config: ~/.openclaw/config/ghost/config.json

Trigger phrases

Load this skill immediately when the user says anything like:

  • "create / write / draft a blog post / article"
  • "publish this post / article", "make this live on Ghost"
  • "update / edit [post title or slug]", "change the title of my post"
  • "unpublish / revert to draft [post]"
  • "create / add a tag", "list my tags on Ghost"
  • "create / update / publish a page"
  • "upload this image to Ghost"
  • "list my posts / drafts", "show me what's in draft"
  • "schedule this post for [date]"
  • "what's on my Ghost site?", "show site info"

Quick Start

bash
python3 scripts/ghost.py config    # verify credentials + active config
python3 scripts/ghost.py site      # test connection + show site info
python3 scripts/ghost.py posts --limit 3 --fields "id,title,status"

Setup

bash
python3 scripts/setup.py       # interactive: credentials + permissions + connection test
python3 scripts/init.py        # validate all configured permissions against live instance

init.py only runs write/delete tests when allow_delete=true. When allow_delete=false, write tests are skipped - no test artifacts are created, so none can be left behind.

Manual - ~/.openclaw/secrets/ghost_creds (chmod 600):

text
GHOST_URL=https://your-ghost.example.com
GHOST_ADMIN_KEY=id:secret_hex
Admin API Key: Ghost Admin → Settings → Integrations → Add custom integration → copy Admin API Key.

config.json - behavior restrictions:

KeyDefaultEffect
allow_publishfalseallow status=published (enable explicitly to publish)
allow_deletefalseallow delete posts/pages/tags
allow_member_accessfalseenable member read/write
default_status"draft"status applied when not specified
default_tags[]tags always merged into new posts
readonly_modefalseoverride: block all writes

Storage & credentials

The skill reads and writes the following paths. All usage is intentional and documented:

PathWritten byPurpose
~/.openclaw/secrets/ghost_credssetup.pyGhost credentials (GHOST_URL, GHOST_ADMIN_KEY). chmod 600. Never committed.
~/.openclaw/config/ghost/config.jsonsetup.pyBehavior restrictions (allow_publish, allow_delete, etc.). No secrets. Not in skill dir - survives clawhub updates.
Credentials can also be provided via environment variables (GHOST_URL, GHOST_ADMIN_KEY). The skill checks env vars first.

Cleanup on uninstall: clawhub uninstall ghost-admin removes the skill directory. To also remove credentials and config:

bash
python3 scripts/setup.py --cleanup
On reinstall, any existing config at ~/.openclaw/config/ghost/config.json is picked up automatically.

Module usage

python
from scripts.ghost import GhostClient
gc = GhostClient()
post = gc.create_post("My Title", html="<p>Body</p>", status="draft")
gc.publish_post(post["id"])

CLI reference

bash
# Posts
python3 scripts/ghost.py posts --status published --limit 10
python3 scripts/ghost.py posts --tag devops --fields "id,title,published_at"
python3 scripts/ghost.py post <id_or_slug>
python3 scripts/ghost.py post-create "Title" --html "<p>...</p>" --status draft
python3 scripts/ghost.py post-create "Title" --html-file body.html --tags "DevOps,Linux"
python3 scripts/ghost.py post-create "Title" --html-file body.html --feature-image "https://..."
python3 scripts/ghost.py post-update <id> --fields-json '{"title":"New","custom_excerpt":"..."}'
python3 scripts/ghost.py post-publish <id>
python3 scripts/ghost.py post-unpublish <id>
python3 scripts/ghost.py post-delete <id>          # requires allow_delete: true

# Pages
python3 scripts/ghost.py pages
python3 scripts/ghost.py page-create "About" --html "<p>...</p>"
python3 scripts/ghost.py page-publish <id>

# Tags
python3 scripts/ghost.py tags
python3 scripts/ghost.py tag-create "DevOps" --slug devops --desc "DevOps content"
python3 scripts/ghost.py tag-delete <id>           # requires allow_delete: true

# Images
python3 scripts/ghost.py image-upload ./image.png --alt "Description"

# Members & newsletters (read)
python3 scripts/ghost.py members --limit 20        # requires allow_member_access: true
python3 scripts/ghost.py newsletters
python3 scripts/ghost.py tiers

# Account
python3 scripts/ghost.py site
python3 scripts/ghost.py me
python3 scripts/ghost.py config

Templates

Draft → review → publish

python
gc = GhostClient()
# 1. Create draft
post = gc.create_post(
    title="My Article",
    html=article_html,
    tags=[{"name": "DevOps"}, {"name": "Linux"}],
    meta_description="Short SEO description",
    status="draft",
)
draft_url = f"{gc.base_url}/ghost/#/editor/post/{post['id']}"
# 2. Return preview link to user for review
# → f"Draft created: {draft_url}"
# 3. After user approval:
gc.publish_post(post["id"])
# → f"Published: {post['url']}"

Research → structured post

python
# Create a post with full SEO metadata
post = gc.create_post(
    title="Why Rust is Taking Over Systems Programming",
    html=content_html,
    custom_excerpt="A technical deep-dive into Rust's memory model and adoption trends.",
    meta_title="Rust Systems Programming 2025",
    meta_description="Why Rust is replacing C++ in systems programming in 2025.",
    og_title="Rust is Taking Over Systems Programming",
    tags=[{"name": "Rust"}, {"name": "Systems"}],
    feature_image="https://example.com/rust.png",
    status="draft",
)

Content audit by tag

python
result = gc.list_posts(limit="all", tag="devops", fields="id,title,status,published_at")
posts  = result["posts"]
drafts    = [p for p in posts if p["status"] == "draft"]
published = [p for p in posts if p["status"] == "published"]
# → summarize backlog for user

Batch tag creation

python
for name in ["DevOps", "Security", "Linux", "Cloud"]:
    try:
        gc.create_tag(name, slug=name.lower())
    except Exception:
        pass  # already exists

Ideas

  • Set allow_publish: false + default_status: draft for a "suggest only" mode
  • Use default_tags in config for auto-tagging (e.g. always add "AI-assisted")
  • Draft from a research summary, share preview link, publish after human review
  • List posts --status draft to surface the content backlog for triage
  • Upload a feature image first, then embed the returned URL in post HTML

Notes

  • updated_at conflict guard: update_post/update_page auto-fetches updated_at if omitted.
  • HTML content: Ghost v5 stores Lexical internally but html import works perfectly for agent-generated content.
  • allow_publish: false: Status is silently capped to "draft" - no error raised.
  • JWT tokens: Generated fresh per request (5-min TTL), no caching needed.
  • Slug: Auto-generated from title if omitted. Override with --slug for clean URLs.

Combine with

SkillWorkflow
summarizeSummarize a URL / PDF → draft a Ghost post from the summary
tavily-searchResearch a topic → structured Ghost draft with sources
nextcloudSave draft .md to NC → review → publish to Ghost
gmailForward a newsletter / article → draft Ghost post from it
api-gateway (linkedin)Publish Ghost post → cross-post excerpt to LinkedIn

API reference

See references/api.md for endpoint details, NQL filters, field list, and error codes.

Troubleshooting

See references/troubleshooting.md for common errors and fixes.

Installation

Terminal bash

openclaw install ghost-admin
    
Copied!

💻Code Examples

python3 scripts/init.py # validate all configured permissions against live instance

python3-scriptsinitpy--validate-all-configured-permissions-against-live-instance.txt
> init.py only runs write/delete tests when `allow_delete=true`. When `allow_delete=false`, write tests are skipped - no test artifacts are created, so none can be left behind.

**Manual** - `~/.openclaw/secrets/ghost_creds` (chmod 600):

GHOST_ADMIN_KEY=id:secret_hex

ghostadminkeyidsecrethex.txt
Admin API Key: Ghost Admin → Settings → Integrations → Add custom integration → copy **Admin API Key**.

**config.json** - behavior restrictions:

| Key | Default | Effect |
|-----|---------|--------|
| `allow_publish` | `false` | allow status=published (enable explicitly to publish) |
| `allow_delete` | `false` | allow delete posts/pages/tags |
| `allow_member_access` | `false` | enable member read/write |
| `default_status` | `"draft"` | status applied when not specified |
| `default_tags` | `[]` | tags always merged into new posts |
| `readonly_mode` | `false` | override: block all writes |

## Storage & credentials

The skill reads and writes the following paths. All usage is intentional and documented:

| Path | Written by | Purpose |
|------|-----------|---------|
| `~/.openclaw/secrets/ghost_creds` | `setup.py` | Ghost credentials (GHOST_URL, GHOST_ADMIN_KEY). chmod 600. Never committed. |
| `~/.openclaw/config/ghost/config.json` | `setup.py` | Behavior restrictions (allow_publish, allow_delete, etc.). No secrets. Not in skill dir - survives clawhub updates. |

Credentials can also be provided via environment variables (`GHOST_URL`, `GHOST_ADMIN_KEY`). The skill checks env vars first.

**Cleanup on uninstall:** `clawhub uninstall ghost-admin` removes the skill directory. To also remove credentials and config:

python3 scripts/setup.py --cleanup

python3-scriptssetuppy---cleanup.txt
On reinstall, any existing config at `~/.openclaw/config/ghost/config.json` is picked up automatically.

## Module usage

python3 scripts/ghost.py config

python3-scriptsghostpy-config.txt
## Templates

### Draft → review → publish
example.sh
python3 scripts/ghost.py config    # verify credentials + active config
python3 scripts/ghost.py site      # test connection + show site info
python3 scripts/ghost.py posts --limit 3 --fields "id,title,status"
example.py
from scripts.ghost import GhostClient
gc = GhostClient()
post = gc.create_post("My Title", html="<p>Body</p>", status="draft")
gc.publish_post(post["id"])
example.sh
# Posts
python3 scripts/ghost.py posts --status published --limit 10
python3 scripts/ghost.py posts --tag devops --fields "id,title,published_at"
python3 scripts/ghost.py post <id_or_slug>
python3 scripts/ghost.py post-create "Title" --html "<p>...</p>" --status draft
python3 scripts/ghost.py post-create "Title" --html-file body.html --tags "DevOps,Linux"
python3 scripts/ghost.py post-create "Title" --html-file body.html --feature-image "https://..."
python3 scripts/ghost.py post-update <id> --fields-json '{"title":"New","custom_excerpt":"..."}'
python3 scripts/ghost.py post-publish <id>
python3 scripts/ghost.py post-unpublish <id>
python3 scripts/ghost.py post-delete <id>          # requires allow_delete: true

# Pages
python3 scripts/ghost.py pages
python3 scripts/ghost.py page-create "About" --html "<p>...</p>"
python3 scripts/ghost.py page-publish <id>

# Tags
python3 scripts/ghost.py tags
python3 scripts/ghost.py tag-create "DevOps" --slug devops --desc "DevOps content"
python3 scripts/ghost.py tag-delete <id>           # requires allow_delete: true

# Images
python3 scripts/ghost.py image-upload ./image.png --alt "Description"

# Members & newsletters (read)
python3 scripts/ghost.py members --limit 20        # requires allow_member_access: true
python3 scripts/ghost.py newsletters
python3 scripts/ghost.py tiers

# Account
python3 scripts/ghost.py site
python3 scripts/ghost.py me
python3 scripts/ghost.py config
example.py
gc = GhostClient()
# 1. Create draft
post = gc.create_post(
    title="My Article",
    html=article_html,
    tags=[{"name": "DevOps"}, {"name": "Linux"}],
    meta_description="Short SEO description",
    status="draft",
)
draft_url = f"{gc.base_url}/ghost/#/editor/post/{post['id']}"
# 2. Return preview link to user for review
# → f"Draft created: {draft_url}"
# 3. After user approval:
gc.publish_post(post["id"])
# → f"Published: {post['url']}"
example.py
# Create a post with full SEO metadata
post = gc.create_post(
    title="Why Rust is Taking Over Systems Programming",
    html=content_html,
    custom_excerpt="A technical deep-dive into Rust's memory model and adoption trends.",
    meta_title="Rust Systems Programming 2025",
    meta_description="Why Rust is replacing C++ in systems programming in 2025.",
    og_title="Rust is Taking Over Systems Programming",
    tags=[{"name": "Rust"}, {"name": "Systems"}],
    feature_image="https://example.com/rust.png",
    status="draft",
)
example.py
result = gc.list_posts(limit="all", tag="devops", fields="id,title,status,published_at")
posts  = result["posts"]
drafts    = [p for p in posts if p["status"] == "draft"]
published = [p for p in posts if p["status"] == "published"]
# → summarize backlog for user

Tags

#web_and-frontend-development #api

Quick Info

Category Development
Model Claude 3.5
Complexity One-Click
Author romain-grosos
Last Updated 3/10/2026
🚀
Optimized for
Claude 3.5
🧠

Ready to Install?

Get started with this skill in seconds

openclaw install ghost-admin