Command line utilities for working with Ghost content.
git clone
this repo &cd
into it as usual- Run
yarn
to install top-level dependencies. - To make
gctools
accessible globally, runnpm link
- You need to run this after making changes to the codebase to update the global version
- If developing the code,
yarn dev ...
is a more suitable command
To see all available tools, run:
gctools
GCTools has an interactive mode which walks you through each tool, without needing to manually type multiple option flags.
gctools i
Available tools include:
zip-split
zip-create
json-split
json-clean
fetch-assets
dedupe-members-csv
random-posts
delete-posts
delete-pages
add-tags
combine-tags
add-preview
delete-tags
delete-labels
delete-empty-tags
find-replace
change-author
add-author
change-visibility-posts
change-visibility-pages
change-status
change-role
add-member-comp-subscription
add-member-comp-from-csv
remove-member-comp-subscription
add-member-newsletter-subscription
change-tags
post-tiers
set-template
page-to-post
content-stats
get-posts
set-featured-images
clean-slugs
set-podcast
revue-stripe
letterdrop-stripe
Each of the tools also has a traditional CLI counterpart with more options, detailed below.
Split a zip file into smaller zip files of a defined maximum size, while maintaining the directory structure.
# See all available options
gctools zip-split --help
# Split a zip file into as many files needed for them to all be 50mb or below
gctools zip-split /path/to/big-file.zip -M 50
Split a large directory into smaller directories of a defined maximum size and zip each, while maintaining the directory structure.
# See all available options
gctools zip-create --help
# Split a large directory into as many files needed for them to all be 50mb or below
gctools zip-create /path/to/big-directory -M 50
Split a large JSON file into smaller JSON files of a defined maximum size, while retaining meta, tag, and author information.
# See all available options
gctools json-split --help
# Split a JSON file into as many files needed for them to hax a maximum of 50 posts per file
gctools json-split /path/to/big-file.json --M 50
Clean a JSON file so it only contains content.
# See all available options
gctools json-clean --help
# Clean a JSON file to only contain content
gctools json-clean /path/to/file.json
Download all available assets from a valid Ghost JSON file create a JSON file with updated image references
# See all available options
gctools fetch-assets --help
# Fetch assets from a valid Ghost JSON file, with `https://example.com` as the base URL
gctools fetch-assets /path/to/file.json https://example.com
Create new CSV files that only contain new or updated members, by comparing the existing members with the output from the output from @tryghost/migrate
.
# See all available options
gctools dedupe-members-csv --help
gctools dedupe-members-csv <existing-members> [new-free] [new-comp] [new-paid]
Insert a number of posts with random content.
# See all available options
gctools random-posts --help
# Create and insert 10 random posts
gctools random-posts <apiURL> <adminAPIKey>
# Create and insert 3000 random draft posts with 2 tags visible to members only, written by a specific author
gctools random-posts <apiURL> <adminAPIKey> --count 3000 --tag '#random,New World' --status draft --visibility members --author [email protected]
# Customize the content length and structure
gctools random-posts <apiURL> <adminAPIKey> --count 50 --contentUnit paragraphs --contentCount 5 --titleMinLength 2 --titleMaxLength 6
Available options:
--count
(default: 10): Number of posts to create--titleMinLength
(default: 3): Minimum words in title--titleMaxLength
(default: 8): Maximum words in title--contentUnit
(default: paragraphs): Content unit type (paragraphs, sentences, words)--contentCount
(default: 10): Number of content units per post--paragraphLowerBound
(default: 3): Min sentences per paragraph--paragraphUpperBound
(default: 7): Max sentences per paragraph--sentenceLowerBound
(default: 3): Min words per sentence--sentenceUpperBound
(default: 15): Max words per sentence--author
: Author email address--tag
(default: #gctools): Comma separated list of tags--status
(default: published): Post status (published, draft, scheduled, sent)--visibility
(default: public): Post visibility (public, members, paid)--delayBetweenCalls
(default: 50): Delay between API calls in ms
Delete all content or content with a specific set of filters, which can be combined.
# See all available options
gctools delete-posts --help
# Delete all posts (⛔️ dangerous!)
gctools delete-posts <apiURL> <adminAPIKey>
# Delete all posts with a specific tag
gctools delete-posts <apiURL> <adminAPIKey> --tag 'hash-testing'
# Delete all published posts
gctools delete-posts <apiURL> <adminAPIKey> --status 'published'
# Delete all draft posts
gctools delete-posts <apiURL> <adminAPIKey> --status 'draft'
# Delete all posts by a specific author
gctools delete-posts <apiURL> <adminAPIKey> --author 'sample-user'
# Delete all posts by a specific author with a specific tag
gctools delete-posts <apiURL> <adminAPIKey> --author 'sample-user' --tag 'hash-testing'
Delete all content or content with a specific set of filters, which can be combined.
# See all available options
gctools delete-pages --help
# Delete all pages (⛔️ dangerous!)
gctools delete-pages <apiURL> <adminAPIKey>
# Delete all pages with a specific tag
gctools delete-pages <apiURL> <adminAPIKey> --tag 'hash-testing'
# Delete all published pages
gctools delete-pages <apiURL> <adminAPIKey> --status 'published'
# Delete all draft pages
gctools delete-pages <apiURL> <adminAPIKey> --status 'draft'
# Delete all pages by a specific author
gctools delete-pages <apiURL> <adminAPIKey> --author 'sample-user'
# Delete all pages by a specific author with a specific tag
gctools delete-pages <apiURL> <adminAPIKey> --author 'sample-user' --tag 'hash-testing'
Add a tag to specific posts and pages with a specific set of filters
# Add a tag of 'Testing' to all posts and pages
gctools add-tags <apiURL> <adminAPIKey> --new_tags 'Testing'
# Add a tag of 'Testing' to all posts and pages (same result as above)
gctools add-tags <apiURL> <adminAPIKey> --new_tags 'Testing' --type posts pages
# Add a tag of 'Testing' to all posts only
gctools add-tags <apiURL> <adminAPIKey> --new_tags 'Testing' --type posts
# Add a tag of 'Testing' to all pages only
gctools add-tags <apiURL> <adminAPIKey> --new_tags 'Testing' --type pages
# Add a tag of 'Testing' to all public posts and pages
gctools add-tags <apiURL> <adminAPIKey> --visibility public --new_tags 'Testing'
# Add a tag of 'Testing' to all members-only posts and pages that also have a tag of 'hello'
gctools add-tags <apiURL> <adminAPIKey> --visibility members --tag 'hello' --new_tags 'Testing'
# Add a tag of 'Testing' to all members-only posts and pages that also have a tag of 'hello', and are by written by 'harry'
gctools add-tags <apiURL> <adminAPIKey> --visibility members --tag 'hello' --author 'harry' --new_tags 'Testing'
Combine tags by adding the target
tag to all posts that hav any of the incorporate
tags. For example, posts with the posts
, newsletter
, and blogs
tags will have the articles
added to it.
gctools combine-tags <apiURL> <adminAPIKey> <jsonFile>
The <jsonFile>
file needs to follow the format below, with slugs used as the values.
[
{
"target": "articles",
"incorporate": [
"posts",
"newsletter",
"blogs"
]
},
{
"target": "media",
"incorporate": [
"podcasts",
"video"
]
}
]
Insert a public preview divider at a specific point, after the previewPosition
number.
# Add a divider to all posts as position 2
gctools add-preview <apiURL> <adminAPIKey> --previewPosition 2
# Add a divider to all posts 50% through the post
gctools add-preview <apiURL> <adminAPIKey> --previewPosition 50%
# Add a divider to all posts as position 2 for members-only posts
gctools add-preview <apiURL> <adminAPIKey> --visibility members --previewPosition 2
# Add a divider to all posts as position 2 for paid posts
gctools add-preview <apiURL> <adminAPIKey> --visibility paid --previewPosition 2
# Add a divider to all posts as position 2 for paid posts that also have a tag of `hello`
gctools add-preview <apiURL> <adminAPIKey> --visibility paid --tag hello --previewPosition 2
# Add a divider to all posts as position 2 for paid posts that also have a tag of `hello`, and are by written by `harry`
gctools add-preview <apiURL> <adminAPIKey> --visibility paid --tag hello --author harry --previewPosition 2
Delete tags, but not the content that uses that tag
# See all available options
gctools delete-tags --help
# Delete a specific tag or multiple tags
gctools delete-tags <apiURL> <adminAPIKey> --tags hash-gctools, test 1
Delete member labels
# See all available options
gctools delete-labels --help
# Delete a specific tag or multiple labels
gctools delete-labels <apiURL> <adminAPIKey> --labels 'First' 'Second'
Delete tags that have no or a low number of associated posts
# See all available options
gctools delete-empty-tags --help
# Delete tags with no associated posts
gctools delete-empty-tags <apiURL> <adminAPIKey>
# Delete tags with 3 or fewer associated posts
gctools delete-empty-tags <apiURL> <adminAPIKey> --maxPostCount 3
# Custom delay between API calls
gctools delete-empty-tags <apiURL> <adminAPIKey> --maxPostCount 5 --delayBetweenCalls 100
Available options:
--maxPostCount
(default: 0): Maximum number of associated posts a tag can have to be deleted--delayBetweenCalls
(default: 50): Delay between API calls in ms
Find & replace strings of text within Ghost posts
# See all available options
gctools find-replace --help
# Replace a string but only in the `mobiledoc` and `title`
gctools find-replace <apiURL> <adminAPIKey> --find 'Old text' --replace 'New text' --where mobiledoc,title
# Replace a string in all available fields
gctools find-replace <apiURL> <adminAPIKey> --find 'Old text' --replace 'New text' --where all
# Custom delay between API calls
gctools find-replace <apiURL> <adminAPIKey> --find 'Old text' --replace 'New text' --delayBetweenCalls 100
Available where
fields are:
all
mobiledoc
(default)html
lexical
title
slug
custom_excerpt
meta_title
meta_description
twitter_title
twitter_description
og_title
og_description
Change the author assigned to a post
# See all available options
gctools change-author --help
# Change the posts written by `richard` and assign to `michael`
gctools change-author <apiURL> <adminAPIKey> --author 'richard' --new_author 'michael'
Add an author to a post
# See all available options
gctools add-author --help
# Add author with the slug 'michael' to all posts
gctools add-author <apiURL> <adminAPIKey> --new_author 'michael'
# Add author with the slug 'michael' to the posts with the tag 'news`
gctools add-author <apiURL> <adminAPIKey> --tag 'news' --new_author 'michael'
# For posts that have 'richard' as an author and with the tag 'news', add 'michael' as an author
gctools add-author <apiURL> <adminAPIKey> --author 'richard' --tag 'news' --new_author 'michael'
Change the visibility of posts
# See all available options
gctools change-visibility-posts --help
# Change the posts that are currently public to be members-only
gctools change-visibility-posts <apiURL> <adminAPIKey> --visibility 'public' --new_visibility 'members'
# Change the posts that are currently members-only to be paid-members only
gctools change-visibility-posts <apiURL> <adminAPIKey> --visibility 'members' --new_visibility 'paid'
# Change the posts tagged with 'news' to be paid-members only
gctools change-visibility-posts <apiURL> <adminAPIKey> --tag 'news' --new_visibility 'paid'
# Change the posts tagged with 'news', and written by 'jane' to be paid-members only
gctools change-visibility-posts <apiURL> <adminAPIKey> --tag 'news' --author 'jane' --new_visibility 'paid'
Change the visibility of pages
# See all available options
gctools change-visibility-pages --help
# Change the pages that are currently public to be members-only
gctools change-visibility-pages <apiURL> <adminAPIKey> --visibility 'public' --new_visibility 'members'
# Change the pages that are currently members-only to be paid-members only
gctools change-visibility-pages <apiURL> <adminAPIKey> --visibility 'members' --new_visibility 'paid'
# Change the pages tagged with 'news' to be paid-members only
gctools change-visibility-pages <apiURL> <adminAPIKey> --tag 'news' --new_visibility 'paid'
# Change the pages tagged with 'news', and written by 'jane' to be paid-members only
gctools change-visibility-pages <apiURL> <adminAPIKey> --tag 'news' --author 'jane' --new_visibility 'paid'
Change the status of posts
# See all available options
gctools change-status --help
# Change the posts that are currently drafts to be public
gctools change-status <apiURL> <adminAPIKey> --status 'draft' --new_status 'published'
# Change the posts that are currently drafts with the tag `news` to be public
gctools change-status <apiURL> <adminAPIKey> --status 'draft' --tag 'news' --new_status 'published'
Change the staff user role (requires a staff user token) [Ghost >= 5.2.0]
# See all available options
gctools change-role <apiURL> <adminAPIKey> --help
# Change all staff users (except the site owner) to have the Contributor role
gctools change-role <apiURL> <adminAPIKey> --newRole 'Contributor'
# Change all staff users who are currently the Editor role to have the Author role
gctools change-role <apiURL> <adminAPIKey> --filterRole 'Editor' --newRole 'Author'
Add complimentary subscriptions for members
# Add a complimentary plan to tier ID abcdtierid1234 that expired on May 4th 2025, but only for members with the label slug 'my-member-label-slug'
gctools add-member-comp-subscription <apiURL> <adminAPIKey> --tierId abcdtierid1234 --expireAt '2025-05-04T00:00:00.000Z' --onlyForLabelSlugs my-member-label-slug
# Custom delay between API calls
gctools add-member-comp-subscription <apiURL> <adminAPIKey> --tierId abcdtierid1234 --delayBetweenCalls 200
Available options:
--tierId
: The ID for the tier to add the subscription to--expireAt
: Expiration date in ISO format--onlyForLabelSlugs
: Filter by member label slugs--delayBetweenCalls
(default: 100): Delay between API calls in ms
Add complimentary subscriptions for members from a CSV file
# See all available options
gctools add-member-comp-from-csv --help
# Add complimentary subscriptions from a CSV file
gctools add-member-comp-from-csv <apiURL> <adminAPIKey> <csvPath>
# Custom delay between API calls
gctools add-member-comp-from-csv <apiURL> <adminAPIKey> <csvPath> --delayBetweenCalls 200
The CSV file must have columns: email
, expireAt
, tierName
Available options:
--delayBetweenCalls
(default: 100): Delay between API calls in ms
Remove complimentary subscriptions for members
# Remove a complimentary plan to tier ID abcdtierid1234, but only for members with the label slug 'my-member-label-slug'
gctools remove-member-comp-subscription <apiURL> <adminAPIKey> --tierId abcdtierid1234 --onlyForLabelSlugs my-member-label-slug
# Custom delay between API calls
gctools remove-member-comp-subscription <apiURL> <adminAPIKey> --tierId abcdtierid1234 --delayBetweenCalls 200
Available options:
--tierId
: The ID for the tier to remove the subscription from--onlyForLabelSlugs
: Filter by member label slugs--delayBetweenCalls
(default: 100): Delay between API calls in ms
Add subscription for a specific newsletter
# Add subscription to a specific newsletter to all members
gctools add-member-newsletter-subscription <apiURL> <adminAPIKey> <newsletterID>
# Add subscription to a specific newsletter to all members that have a label slug of 'premium-blog' or 'news'
# Note: Slugs are not the same as names. You can get the label slug by filtering members and checking the URL
gctools add-member-newsletter-subscription <apiURL> <adminAPIKey> <newsletterID> --onlyForLabelSlugs 'premium-blog,news'
Takes a CSV file of URLs, tags to add, and tags to delete. For each URL in there, it will delete and add specific tags.
# Will add the tags to the end of the tag list
gctools change-tags <apiURL> <adminAPIKey> <csvFile>
# Will use the first tag in the `add_tags` list as the primary tag
gctools change-tags <apiURL> <adminAPIKey> <csvFile> --addAsPrimaryTag true
The CSV must follow a specific format:
url,delete_tags,add_tags
https://example.com/my-post-slug/,Newsletter,"News, Blogs"
... as many rows as needed
In this CSV, the first post will have 'Newsletter` removed, and both 'News' & 'Blogs' added to the end the tag list.
If --addAsPrimaryTag true
is set, 'News' & 'Blogs' will be added to the start of the tag list, making 'News' the new primary tag.
Adds an additional tier to posts that are already set to show to a specific tier.
# Will add the tier 5678bcde to all posts that currently also have the tier abcd1234
gctools post-tiers <apiURL> <adminAPIKey> --filterTierId abcd1234 --addTierId 5678bcde
# Will add the tier 5678bcde to all posts that are set to 'Paid-members only'
gctools post-tiers <apiURL> <adminAPIKey> --visibility paid --addTierId 5678bcde
Set posts to use a specific custom template.
# Set all posts to use the default template
gctools set-template <apiURL> <adminAPIKey> --templateSlug default
# Set all posts to use the template that has the filename `custom-posts-sidebar.hbs`
gctools set-template <apiURL> <adminAPIKey> --templateSlug custom-posts-sidebar
# Set all posts to use the template that has the filename `custom-posts-sidebar.hbs`, if it has the tag 'news'
gctools set-template <apiURL> <adminAPIKey> --tag 'news' --templateSlug custom-posts-sidebar
Changes a page (or selection of pages) to a post(s).
# Will change _all_ pages to posts
gctools page-to-post <apiURL> <adminAPIKey>
# Will change a single page to a post
gctools page-to-post <apiURL> <adminAPIKey> --id abcd123480830d8dd2b7652c
# Will change any page with this lat slug to a post
gctools page-to-post <apiURL> <adminAPIKey> --tagSlug 'my-tag-slug'
Display statistics about the content in your Ghost site.
# See all available options
gctools content-stats --help
# Show basic content statistics
gctools content-stats <apiURL> <adminAPIKey>
# Show content statistics and list authors with no posts
gctools content-stats <apiURL> <adminAPIKey> --listEmptyAuthors
This command displays:
- Total number of posts, pages, authors, tags, and members
- Post status breakdown (published, draft, etc.)
- Visibility breakdown (public, members, paid)
- Author statistics
Available options:
--listEmptyAuthors
: List authors who have no posts
Retrieve all posts from Ghost (useful for debugging or data export).
# See all available options
gctools get-posts --help
# Get all posts
gctools get-posts <apiURL> <adminAPIKey>
# Custom delay between API calls
gctools get-posts <apiURL> <adminAPIKey> --delayBetweenCalls 100
Available options:
--delayBetweenCalls
(default: 50): Delay between API calls in ms
Set featured images for posts that don't have one by using the first image found in the post content.
# See all available options
gctools set-featured-images --help
# Set featured images for posts without them
gctools set-featured-images <apiURL> <adminAPIKey>
# Custom delay between API calls
gctools set-featured-images <apiURL> <adminAPIKey> --delayBetweenCalls 100
Available options:
--delayBetweenCalls
(default: 50): Delay between API calls in ms
Find and remove alphanumeric IDs from post slugs to make them cleaner and more SEO-friendly.
# See all available options
gctools clean-slugs --help
# Clean slugs by removing alphanumeric IDs
gctools clean-slugs <apiURL> <adminAPIKey>
# Dry run to see what would be changed without making changes
gctools clean-slugs <apiURL> <adminAPIKey> --dry-run
# Custom delay between API calls
gctools clean-slugs <apiURL> <adminAPIKey> --delayBetweenCalls 100
This command identifies and removes alphanumeric ID patterns from post slugs that may have been automatically generated, making the URLs cleaner and more human-readable.
Available options:
--dry-run
: Show what would be changed without making actual changes--delayBetweenCalls
(default: 50): Delay between API calls in ms
Set Facebook description for podcast posts using the first audio source URL found in the post content.
# See all available options
gctools set-podcast --help
# Set Facebook descriptions for podcast posts
gctools set-podcast <apiURL> <adminAPIKey>
# Custom delay between API calls
gctools set-podcast <apiURL> <adminAPIKey> --delayBetweenCalls 100
This command automatically finds the first audio element in post content and uses its source URL to populate the Facebook description field, which is useful for Ghost's podcast workaround
Available options:
--delayBetweenCalls
(default: 50): Delay between API calls in ms
Add Stripe customer IDs to Revue subscriber export (Beta feature).
# See all available options
gctools revue-stripe --help
# Combine Revue and Stripe data
gctools revue-stripe <revueCSV> <stripeCSV>
This is a beta feature for combining Revue subscriber data with Stripe customer information.
Add Stripe customer IDs to Letterdrop subscriber export (Beta feature).
# See all available options
gctools letterdrop-stripe --help
# Combine Letterdrop and Stripe data
gctools letterdrop-stripe <letterdropCSV> <stripeCSV>
# Skip writing CSV files
gctools letterdrop-stripe <letterdropCSV> <stripeCSV> --writeCSVs false
This is a beta feature for combining Letterdrop subscriber data with Stripe customer information.
Available options:
--writeCSVs
(default: true): Whether to create new CSV files
commands
handles the traditional CLI inputprompts
handles the interactive CLI inputtasks
is the tasks run by both the CLI and interactive tool
Copyright (c) 2013-2025 Ghost Foundation - Released under the MIT license.