# Table of Contents - [Ghost: The #1 open source headless Node.js CMS](#ghost-the-1-open-source-headless-node-js-cms) - [Ghost Handlebars Themes - Building a custom Ghost theme - Docs](#ghost-handlebars-themes-building-a-custom-ghost-theme-docs) - [Hosting a Ghost publication - Fully-managed PaaS & self-hosted](#hosting-a-ghost-publication-fully-managed-paas-self-hosted) - [How to install Ghost, the official guide](#how-to-install-ghost-the-official-guide) - [Ghost Developer Docs - Introduction](#ghost-developer-docs-introduction) - [Ghost Architecture – A modern, decoupled web application for professional publishers](#ghost-architecture-a-modern-decoupled-web-application-for-professional-publishers) - [Ghost Docs](#ghost-docs) - [Ghost product principles & roadmap](#ghost-product-principles-roadmap) - [How to Migrate to Ghost from other platforms](#how-to-migrate-to-ghost-from-other-platforms) - [Users & Permissions – Manage your team](#users-permissions-manage-your-team) - [Memberships – Ghost Developer Docs](#memberships-ghost-developer-docs) - [Email Newsletters — Ghost Developer Docs](#email-newsletters-ghost-developer-docs) - [Ghost Security & Privacy](#ghost-security-privacy) - [Publishing Core Concepts – Ghost Developer Docs](#publishing-core-concepts-ghost-developer-docs) - [How to use responsive images in Ghost themes](#how-to-use-responsive-images-in-ghost-themes) - [Ghost Theme Features: The Editor - Documentation](#ghost-theme-features-the-editor-documentation) - [Recommendations – Ghost Developer Docs](#recommendations-ghost-developer-docs) - [Context Overview: Ghost Themes - Documentation](#context-overview-ghost-themes-documentation) - [Ghost Handlebars Theme Structure - Developer Documentation](#ghost-handlebars-theme-structure-developer-documentation) - [Ghost Handlebars Themes - Functional Helpers](#ghost-handlebars-themes-functional-helpers) - [Ghost Theme Development: Adding search to a theme](#ghost-theme-development-adding-search-to-a-theme) - [Ghost Theme Development: Building custom membership flows](#ghost-theme-development-building-custom-membership-flows) - [Configuration - Adapt your publication to suit your needs](#configuration-adapt-your-publication-to-suit-your-needs) - [Validate Ghost theme compatibility](#validate-ghost-theme-compatibility) - [Ghost Themes - Dynamic URLs & Routing](#ghost-themes-dynamic-urls-routing) - [Ghost-CLI - A fully loaded tool for installation and configuration](#ghost-cli-a-fully-loaded-tool-for-installation-and-configuration) - [How to use custom settings in Ghost themes - Developer docs](#how-to-use-custom-settings-in-ghost-themes-developer-docs) - [Ghost Content API Documentation](#ghost-content-api-documentation) - [Using Ghost as a headless CMS with JAMstack](#using-ghost-as-a-headless-cms-with-jamstack) - [Ghost Admin API Documentation](#ghost-admin-api-documentation) - [Contributing to Ghost - Submit your contribution to open source software](#contributing-to-ghost-submit-your-contribution-to-open-source-software) - [Ghost Webhooks](#ghost-webhooks) - [Ghost Docs](#ghost-docs) - [Ghost Docs](#ghost-docs) - [Ghost Docs](#ghost-docs) - [How to install & setup Ghost on Ubuntu 20.04 or 22.04](#how-to-install-setup-ghost-on-ubuntu-20-04-or-22-04) - [Ghost Docs](#ghost-docs) - [Ghost Logos](#ghost-logos) - [Ghost API Versioning](#ghost-api-versioning) - [Supported node versions for self-hosted installs of Ghost](#supported-node-versions-for-self-hosted-installs-of-ghost) - [How to install Ghost locally on Mac, PC or Linux](#how-to-install-ghost-locally-on-mac-pc-or-linux) - [Ghost Docs](#ghost-docs) - [Ghost Updates: How to update to the latest major version](#ghost-updates-how-to-update-to-the-latest-major-version) - [Build A Custom Static Site With Headless Ghost + Gatsby](#build-a-custom-static-site-with-headless-ghost-gatsby) - [How to install Ghost on Digital Ocean - Official guide](#how-to-install-ghost-on-digital-ocean-official-guide) - [Official guide: How to migrate from Substack to Ghost](#official-guide-how-to-migrate-from-substack-to-ghost) - [How to reinstall Ghost](#how-to-reinstall-ghost) - [Content API JavaScript Client](#content-api-javascript-client) - [Official guide: How to migrate from beehiiv to Ghost](#official-guide-how-to-migrate-from-beehiiv-to-ghost) - [Official guide: How to migrate from WordPress to Ghost](#official-guide-how-to-migrate-from-wordpress-to-ghost) - [How to install & setup Ghost on Linode](#how-to-install-setup-ghost-on-linode) - [Supported databases in production for self-hosting Ghost - Ghost Developers](#supported-databases-in-production-for-self-hosting-ghost-ghost-developers) - [Official guide: How to migrate from Newspack to Ghost](#official-guide-how-to-migrate-from-newspack-to-ghost) - [Official guide: How to migrate from Patreon to Ghost](#official-guide-how-to-migrate-from-patreon-to-ghost) - [Major Versions & Long Term Support - Ghost Developers](#major-versions-long-term-support-ghost-developers) - [Official guide: How to migrate from Squarespace to Ghost](#official-guide-how-to-migrate-from-squarespace-to-ghost) - [Official guide: How to migrate from Medium to Ghost](#official-guide-how-to-migrate-from-medium-to-ghost) - [How to migrate data from Ghost to Ghost](#how-to-migrate-data-from-ghost-to-ghost) - [Ghost Handlebars Theme Helpers: search](#ghost-handlebars-theme-helpers-search) - [Official guide: How to migrate from Buttondown to Ghost](#official-guide-how-to-migrate-from-buttondown-to-ghost) - [Official guide: How to migrate from Kit to Ghost](#official-guide-how-to-migrate-from-kit-to-ghost) - [Official guide: How to migrate from Gumroad to Ghost](#official-guide-how-to-migrate-from-gumroad-to-ghost) - [How to backup your self-hosted Ghost install - Ghost Developers](#how-to-backup-your-self-hosted-ghost-install-ghost-developers) - [Official guide: How to migrate from Memberful to Ghost](#official-guide-how-to-migrate-from-memberful-to-ghost) - [Official guide: How to migrate from Jekyll to Ghost](#official-guide-how-to-migrate-from-jekyll-to-ghost) - [Migrating to Ghost - Developer Guide](#migrating-to-ghost-developer-guide) - [Ghost Handlebars Theme Helpers: foreach](#ghost-handlebars-theme-helpers-foreach) - [Official guide: How to migrate from Mailchimp to Ghost](#official-guide-how-to-migrate-from-mailchimp-to-ghost) - [Ghost Handlebars Theme Helpers: if](#ghost-handlebars-theme-helpers-if) - [Ghost Handlebars Theme Helpers: has](#ghost-handlebars-theme-helpers-has) - [Ghost Handlebars Theme Helpers: match](#ghost-handlebars-theme-helpers-match) --- # Ghost: The #1 open source headless Node.js CMS Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Learn how to build and develop beautiful, independent publications Developer install guide ----------------------- Follow our setup guides for any platform, from local development to production environments. [Get started →](/docs/install/) [Ghost(Pro)](https://ghost.org/pricing/) [Ubuntu](/docs/install/ubuntu/) [Local](/docs/install/local/) [Docker](/docs/install/docker/) ![Ghost](/images/home/ghost-admin-home-header.png) [### Platform guide\ \ A detailed overview of Ghost's architecture & configuration.\ \ Read the docs](/docs/product/) [### Migration guide\ \ Import your content, members and payments from other platforms.\ \ Import data](/docs/migration/) [### Theme guide\ \ A full guide to building custom designed templates for your site.\ \ Start building](/docs/themes/) ### Developer resources [**Starter theme framework**\ \ Fast track your development of new custom themes with our open source theme starter framework.](https://github.com/tryghost/starter) [**Ghost API documentation**\ \ Explore detailed REST API documentation for accessing content in and out of Ghost programmatically.](/docs/content-api/) [**JAMstack front-end frameworks**\ \ Use Ghost as a headless CMS, integrating the API with any third party front-end or custom static framework.](/docs/jamstack/) [**Device verification & email 2FA**\ \ 2 minutes ago](https://ghost.org/changelog/2fa/) [**Custom content for every subscriber**\ \ 14 days ago](https://ghost.org/changelog/custom-content-for-every-subscriber/) [**Social web (beta)**\ \ April 1st, 2025](https://ghost.org/changelog/social-web-beta/) [**Signup spam protection**\ \ February 5th, 2025](https://ghost.org/changelog/signup-spam-protection/) [**Upgraded comments**\ \ December 19th, 2024](https://ghost.org/changelog/upgraded-comments/) ### Community [![GitHub](/images/docs/github.svg)\ \ **GitHub** Source code and releases](https://github.com/tryghost) [![Forum](/images/docs/community.svg)\ \ **Developer forum** Official community](https://forum.ghost.org) [![Reddit](/images/docs/reddit.svg)\ \ **Reddit** News and highlights](https://www.reddit.com/r/ghost) [![Twitter](/images/docs/twitter.svg)\ \ **Twitter** Bite-size updates](https://twitter.com/ghost) [![Tutorials](/images/docs/tutorials.svg)\ \ **Tutorials** In-depth guides](/tutorials/) [![StackOverflow](/images/docs/stackoverflow.svg)\ \ **StackOverflow** Questions and answers](https://stackoverflow.com/questions/tagged/ghost) Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Handlebars Themes - Building a custom Ghost theme - Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) The Ghost theme layer has been engineered to give developers and designers the flexibility to build custom publications that are powered by the Ghost platform. Theme development ----------------- Ghost themes use the Handlebars templating language which creates a strong separation between templates (the HTML) and any JavaScript logic with the use of helpers. This allows themes to be super fast, with a dynamic client side app, and server side publication content that is sent to the browser as static HTML. Ghost also makes use of an additional library called `express-hbs` which adds some additional features to Handlebars, such as layouts and partials. If you’ve previously built themes for other popular platforms, working with the Ghost theme layer is extremely accessible. This documentation gives you the tools required to create static HTML and CSS for a theme, using Handlebars expressions when you need to render dynamic data. Our tutorial on the [essential concepts to known when building a Ghost theme](https://ghost.org/tutorials/essential-concepts/) , provides a fantastic introduction to everything you need to know to start building beautiful themes. Handlebars ---------- The Handlebars templating language provides the power to build semantic templates effectively. * [Handlebars documentation](https://handlebarsjs.com/guide/expressions.html) Installation of Handlebars is already done for you in Ghost ✨ Custom settings --------------- Offering customization options to theme users can be done using custom settings. This allows theme developers to empower non-developers to make controlled changes. Head to the [Custom settings documentation](/docs/themes/custom-settings/) to learn more. GScan ----- Validating your Ghost theme is handled efficiently with the [GScan tool](https://gscan.ghost.org/) . GScan will check your theme for errors, deprecations and compatibility issues. * The [GScan site](https://gscan.ghost.org/) is your first port of call to test any themes that you’re building to get a full validation report * When a theme is uploaded in Ghost admin, it will automatically be checked with `gscan` and any fatal errors will prevent the theme from being used * `gscan` is also used as a command line tool ### Command line To use GScan as a command line tool, globally install the `gscan` npm package: # Install the npm package npm install -g gscan # Use gscan anywhere to run gscan against a folder gscan /path/to/ghost/content/themes/casper # Run gscan on a zip file gscan -z /path/to/download/theme.zip What’s next? ------------ That’s all of the background context required to get started. From here, take a look at the [structure](/docs/themes/structure/) of Ghost themes and templates, and learn everything you need to know about the `package.json` file. For community led support about theme development, visit [the forum](https://forum.ghost.org/c/themes/) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Hosting a Ghost publication - Fully-managed PaaS & self-hosted Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) A short guide to running Ghost in a production environment and setting up an independent publication to serve traffic at scale. Ghost is open source software, and can be installed and maintained relatively easily on just about any VPS hosting provider. Additionally, we run an official PaaS for Ghost called [Ghost(Pro)](/pricing/) , where you can have a fully managed instance set up in a couple of clicks. All revenue from Ghost(Pro) goes toward funding the future development of Ghost itself, so by using our official hosting you’ll also be funding developers to continue to improve the core product for you. Ghost(Pro) vs Self-hosting -------------------------- A common question we get from developers is whether they should use our official platform, or host the codebase on their own server independently. Deciding which option is best for you comes with some nuance, so below is a breakdown of the differences to help you decide what will fit your needs best. | | Ghost(Pro) | Self-Hosting | | --- | --- | --- | | 🎛 Product features | Identical | Identical | | ⚙️ Base hosting cost | From **$9**/mo | From **$10**/mo | | 🌍 Worldwide Fastly CDN | Included | From **$50**/mo | | 💌 Email newsletter sending | Included | From **$35**/mo | | 📦 Full site backups | Included | From **$5**/mo | | 🖼️ Built-in image editor | Included | From **$12**/mo | | 💵 Payment processing fees | 0% | 0% | | 🖥 Install & setup | ✅ | Manual | | 🔄 Weekly updates | ✅ | Manual | | 🚧 Server maintenance & updates | ✅ | Manual | | 🔒 SSL Certificate | ✅ | Manual | | ⚠️ Threat & uptime management | ✅ | ❌ | | 🥊 Enterprise-grade security | ✅ | ❌ | | 🔀 Custom edge routing policies | ❌ | ✅ | | 👩‍💻 Direct SSH & DB access | ❌ | ✅ | | 🔨 Ability to modify core | ❌ | ✅ | | 🚑 Ghost product support | Email | Forum | | ❤️ Where your money goes | New features in Ghost | 3rd party companies | ### Which option is best for me? **Self-hosting** is the best choice for teams who are comfortable managing servers, databases and Node.js apps who want full control over their environment. There’s more complexity involved, and you have to signup-for and pay for your own Mailgun account (email delivery) and CDN service (performance and scale) — but ultimately more flexibility around exactly how the software runs. For people sending lots of email newsletters, self-hosting generally works out to be more expensive compared to Ghost(Pro), but for people who aren’t using email functionality, it can work out to roughly the same price. [See self-hosting guides & instructions →](/docs/install/) **Ghost(Pro)** is the best choice for most people who are focused on using the Ghost software, and don’t want to spend time managing servers. Setting up a new Ghost site takes around 20 seconds, and after that all weekly updates, backups, security and performance are managed for you. If your site ever goes down, our team gets woken up while you sleep peacefully. In most cases Ghost(Pro) ends up being lower cost than self-hosting once you add up the cost of the different service providers. [See Ghost(Pro) pricing & plans →](/pricing/) **TLDR:** If you want the easiest, cheapest option: Ghost(Pro) is probably your best bet. If you have a technical team and you want maximum control and flexibility, you’ll get more out of self-hosting. * * * Self-hosting details & configuration ------------------------------------ Ghost has a [small team](/docs/product/) , so we optimize the software for a single, narrow, well defined stack which is heavily tested. This is the same stack that we use on Ghost(Pro), so we can generally guarantee that it’s going to work well. Ghost _can_ also run successfully with different operating systems, databases and web servers, but these are not officially supported or widely adopted, so your mileage may (will) vary. Our officially supported and recommended stack is as follows: * **Ubuntu 16.04**, **18.04**, **20.04** or **22.04** * MySQL 8.0 * NGINX * Systemd * [Recommended Node version](/docs/faq/node-versions/) installed via NodeSource * A server with at least 1GB memory * A non-root user for running `ghost` commands [See self-hosting guides & instructions →](/docs/install/) * * * ### Server hardening After setting up a fresh Ubuntu install in production, it’s worth considering the following steps to make your new environment extra secure and resilient: * **Use SSL** - Ghost should be configured to run over HTTPS. Ghost admin must be run over HTTPS. You can choose to [load admin on a separate domain](/docs/hosting/#separate-admin-domain) for additional security * **Secure MySQL** - We strongly recommend running `mysql_secure_installation` after successful setup to significantly improve the security of your database. * **Set up a firewall** - Ubuntu 18.04, 20.04 and 22.04 servers can use the UFW firewall to make sure only connections to certain services are allowed. We recommend setting up UFW rules for `ssh`, `nginx`, `http`, and `https`. If you do use UFW, make sure you don’t use any other firewalls. * **Disable SSH Root & password logins** - It’s a very good idea to disable SSH password based login and _only_ connect to your server via proper SSH keys. It’s also a good idea to disable the root user. * **Separate admin domain** - Configuring a separate [admin URL](https://ghost.org/docs/config/#admin-url) can help to guard against [privilege escalation](https://ghost.org/docs/security/#privilege-escalation-attacks) and reduces available attack vectors. ### Optimizing for scale The correct way to scale Ghost is by adding a CDN and/or caching layer in front of your Ghost instance. **Clustering or sharding is not supported in any way.** Every day 2-5 of the top stories on Hacker News are published with Ghost, and to the best of our knowledge no Ghost site has ever fallen over as a result of a traffic spike. Minimal, sensible caching is more than enough. ### Staying up to date Whenever running a public-facing production web server it’s **critically important** to keep all software up to date. If you don’t keep everything up to date, you place your site and your server at risk of numerous potential exploits and hacks. If you can’t manage these things yourself, ensure that a systems administrator on your team is able to keep everything updated on your behalf. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to install Ghost, the official guide Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) The fastest way to get started is to set up a site on **Ghost(Pro)**. If you're running a self-hosted instance, we strongly recommend an Ubuntu server with at least 1GB of memory to run Ghost. [![Ubuntu](/images/docs/install/ubuntu-logo.svg)\ \ ### Ubuntu\ \ 16.04, 18.04, 20.04, 22.04 LTS](/docs/install/ubuntu/) [![Docker](/images/docs/install/docker-logo.svg)\ \ ### Docker\ \ Community image](/docs/install/docker/) [![Local install](/images/docs/install/local_hufd41d0e39e6a56af2fade6d8e93ee88b_4742_432x0_resize_q100_h2_box_3.webp)\ \ ### Local install\ \ MacOS, Windows & Linux](/docs/install/local/) [![Install from source](/images/docs/install/source.svg)\ \ ### Install from Source\ \ For working on Ghost Core](/docs/install/source/) Cloud hosting ------------- [![Ghost(Pro)](/images/docs/install/pro-logo.svg)\ \ ### Ghost(Pro)\ \ Official managed hosting](/pricing/) [![Digital Ocean](/images/docs/install/digitalocean-logo.svg)\ \ ### Digital Ocean\ \ Pre-built VPS image](/docs/install/digitalocean/) [![Linode](/images/docs/install/linode-logo.svg)\ \ ### Linode\ \ Virtual private servers](/docs/install/linode/) Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Developer Docs - Introduction Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost is an open source, professional publishing platform built on a modern Node.js technology stack — designed for teams who need power, flexibility and performance. Hitting the right balance of needs has led Ghost to be used in production by organisations including Apple, Sky News, DuckDuckGo, Mozilla, Kickstarter, Square, CloudFlare, Tinder, the Bitcoin Foundation and [many more](/explore/) . Every day Ghost powers some of the most-read stories on the internet, serving hundreds of millions of requests across tens of thousands of sites. How is Ghost different? ----------------------- The first question most people have is, of course, how is Ghost different from everything else out there? Here’s a table to give you a quick summary: | | Ghost
(That's us!) | Open platforms
(eg. WordPress) | Closed platforms
(eg. Substack) | | --- | --- | --- | --- | | 🏎 Exceptionally fast | ✅ | ❌ | ✅ | | 🔒 Reliably secure | ✅ | ❌ | ✅ | | 🎨 Great design | ✅ | ❌ | ✅ | | 👩🏼‍🚀 Modern technology | ✅ | ❌ | ✅ | | ♻️ Open Source | ✅ | ✅ | ❌ | | 🏰 Own your brand+data | ✅ | ✅ | ❌ | | 🌍 Use a custom domain | ✅ | ✅ | ❌ | | 🖼 Control your site design | ✅ | ✅ | ❌ | | 🌱 Censorship-free | ✅ | ✅ | ❌ | | ⭐️ Built-in SEO control | ✅ | ❌ | ❌ | | 🚀 Native REST API | ✅ | ❌ | ❌ | | 🛠 Comprehensive SDK | ✅ | ❌ | ❌ | | 🛒 Built-in membership & subscription
commerce features | ✅ | ❌ | ❌ | | 🤝 Works with any front-end
or static site framework | ✅ | ❌ | ❌ | | ❤️ Non-profit organisation with
a sustainable business model | ✅ | ❌ | ❌ | **In short:** Other open platforms are generally old, slow and bloated, while other closed platforms give you absolutely no control or ownership of your content. Ghost provides the best of both worlds, and more. Still after a more detailed comparison? We’ve got some in-depth articles comparing Ghost to [WordPress](/vs/wordpress/) , [Medium](/vs/medium/) and [Tumblr](/vs/tumblr/) . Background ---------- Ghost was created by [John O’Nolan](https://twitter.com/johnonolan) and [Hannah Wolfe](https://twitter.com/erisds) in 2013 following a runaway Kickstarter campaign to create a new, modern publishing platform to serve professional publishers. Previously, John was a core contributor of WordPress and watched as the platform grew more complicated and less focused over time. Ghost started out as a little idea to be the antidote to that pain, and quickly grew in popularity as the demand for a modern open source solution became evident. Today, Ghost is one of the most popular open source projects in the world - the **#1** CMS [on GitHub](https://github.com/tryghost/ghost) - and is used in production by millions of people. More than anything, we approach building Ghost to create the product we’ve always wanted to use, the company we’ve always wanted to do business with, and the environment we’ve always wanted to work in. So, we do things a little differently to most others: #### Independent structure Ghost is structured as a [non-profit organisation](/about/) to ensure it can legally never be sold and will always remain independent, building products based on the needs of its users - _not_ the whims of investors looking for 💰 returns. #### Sustainable business While the software we release is free, we also sell [premium managed hosting](/pricing/) for it, which gives the non-profit organisation a sustainable business model and allows it to be 100% self-funded. #### Distributed team Having a sustainable business allows us to hire open source contributors to work on Ghost full-time, and we do this [entirely remotely](/about/#careers) . The core Ghost team is fully distributed and live wherever they choose. #### Transparent by default We share [our revenue](/about/) transparently and [our code](https://github.com/tryghost) openly so anyone can verify what we do and how we do it. No cloaks or daggers. #### Unconditional open source All our projects are released under the permissive open source [MIT licence](https://en.wikipedia.org/wiki/MIT_License) , so that even if the company were to fail, our code could still be picked up and carried on by anyone in the world without restriction. Features -------- Ghost comes with powerful features built directly into the core software which can be customised and configured based on the needs of each individual site. Here’s a quick overview of the main features you’ll probably be interested in as you’re getting started. This isn’t an exhaustive list, just some highlights. ### Built-in memberships & subscriptions Don’t just create content for anonymous visitors, Ghost lets you turn your audience into a business with native support for member signups and paid subscription commerce. It’s the only platform with memberships built in by default, and deeply integrated. Check out our [membership guide](/docs/members/) for more details. ### Developer-friendly API At its core Ghost is a self-consuming, RESTful JSON API with decoupled admin client and front-end. We provide lots of tooling to get a site running as quickly as possible, but at the end of the day it’s **Just JSON** ™️, so if you want to use Ghost completely headless and write your own frontend or backend… you can! Equally, Ghost is heavily designed for performance. There are 2-5 frontpage stories on HackerNews at any given time that are served by Ghost. It handles scale with ease and doesn’t fall over as a result of traffic spikes. ### A serious editor Ghost has the rich editor that every writer wants, but under the hood it delivers far more power than you would expect. All content is stored in a standardised JSON-based document storage format called Lexical, which includes support for extensible rich media objects called Cards. In simple terms you can think of it like having Slack integrations inside Medium’s editor, stored sanely and fully accessible via API. ### Custom site structures Routing in Ghost is completely configurable based on your needs. Out of the box Ghost comes with a standard reverse chronological feed of posts with clean permalinks and basic pages, but that’s easy to change. Whether you need a full **multi-language site** with `/en/` and `/de/` base URLs, or you want to build out specific directory structures for hierarchical data like `/europe/uk/london/` — Ghost’s routing layer can be manipulated in any number of ways to achieve your use case. ### Roles & permissions Set up your site with sensible user roles and permissions built-in from the start. * **Contributors:** Can log in and write posts, but cannot publish. * **Authors:** Can create and publish new posts and tags. * **Editors:** Can invite, manage and edit authors and contributors. * **Administrators:** Have full permissions to edit all data and settings. * **Owner:** An admin who cannot be deleted + has access to billing details. ### Custom themes Ghost ships with a simple Handlebars.js front-end theme layer which is very straightforward to work with and surprisingly powerful. Many people stick with the default theme ([live demo](https://demo.ghost.io) / [source code](https://github.com/tryghost/casper) ), which provides a clean magazine design - but this can be modified or entirely replaced. The Ghost [Theme Marketplace](/marketplace/) provides a selection of pre-made third-party themes which can be installed with ease. Of course you can also build your own [Handlebars Theme](/docs/themes/) or use a [different front-end](/docs/content-api/) altogether. ### Apps & integrations Because Ghost is completely open source, built as a JSON API, has webhooks, and gives you full control over the front-end: It essentially integrates with absolutely everything. Some things are easier than others, but almost anything is possible with a little elbow grease. Or a metaphor more recent than 1803. You can browse our large [directory of integrations](/integrations/) with instructions, or build any manner of custom integration yourself by writing a little JavaScript and Markup to do whatever you want. You don’t need janky broken plugins which slow your site down. Integrations are the modern way to achieve extended functionality with ease. ### Search engine optimisation Ghost comes with world-class SEO and everything you need to ensure that your content shows up in search indexes quickly and consistently. ##### No plugins needed Ghost has all the fundamental technical SEO optimisations built directly into core, without any need to rely on third party plugins. It also has a far superior speed and pageload performance thanks to Node.js. ##### Automatic google XML sitemaps Ghost will automatically generate and link to a complete Google sitemap including every page on your site, to make sure search engines are able to index every URL. ##### Automatic structured data + JSON-LD Ghost generates [JSON-LD](https://developers.google.com/search/docs/guides/intro-structured-data) based structured metadata about your pages so that you don’t have to rely on messy microformats in your markup to provide semantic context. Even if you change theme or front-end, your SEO remains perfectly intact. Ghost also adds automatic code for Facebook OpenGraph and Twitter Cards. ##### Canonical tags Ghost automatically generates the correct `rel="canonical"` tag for each post and page so that search engines always prioritise one true link. * * * If you’re curious to see more, check out the [features page](/features/) on Ghost.org. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Architecture – A modern, decoupled web application for professional publishers Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost is structured as a modern, decoupled web application with a sensible service-based architecture. 1. **A robust core JSON API** 2. **A beautiful admin client app** 3. **A simple, powerful front-end theme layer** These three areas work together to make every Ghost site function smoothly, but because they’re decoupled there’s plenty of room for customisation. * * * ### How things fit together ![Ghost Architecture](/images/docs/concepts/ghost-architecture_hu6c4bbd92be6e8f2ca966b7ab8d796eb5_275325_1000x0_resize_q100_h2_box_3.webp) Physically, the Ghost codebase is structured in two main directories: * `core` - Contains the core files which make up Ghost * `content` - Contains the files which may be added or changed by the user such as themes and images #### Data & Storage Ghost ships with the [Bookshelf.js ORM](https://bookshelfjs.org/) layer by default allowing for a range of databases to be used. Currently SQLite3 is the supported default in development while MySQL is recommended for production. Other databases are available, and compatible, but not supported by the core team. Additionally, while Ghost uses local file storage by default it’s also possible to use custom storage adapters to make your filesystem completely external. There are fairly wide range of pre-made [storage adapters for Ghost](/integrations/?tag=storage) already available for use. #### Ghost-CLI Orchestrating these different components is done via a comprehensive CLI and set of utilities to keep everything running and up to date. #### Philosophy Ghost is architected to be familiar and easy to work with for teams who are already used to working with JavaScript based codebases, whilst still being accessible to a broad audience. It’s neither the most bleeding-edge structure in the world, nor the most simple, but strives to be right balance between the two. > You can help build the future. Ghost is currently hiring Product Engineers - check out what it’s like to be part of the team and see our open roles at [careers.ghost.org](https://careers.ghost.org/) * * * Ghost Core ---------- At its heart, Ghost is a RESTful JSON API — designed to create, manage and retrieve publication content with ease. Ghost’s API is split by function into two parts: Content and Admin. Each has its own authentication methods, structure and extensive tooling so that common publication usecases are solved with minimal effort. Whether you want to publish content from your favourite desktop editor, build a custom interface for handling editorial workflow, share your most recent posts on your marketing site, or use Ghost as a full headless CMS, Ghost has the tools to support you. ### Content API Ghost’s public Content API is what delivers published content to the world and can be accessed in a read-only manner by any client to render in a website, app or other embedded media. Access control is managed via an API key, and even the most complex filters are made simple with our [query language](/docs/content-api/#filtering) . The Content API is designed to be fully cachable, meaning you can fetch data as often as you like without limitation. ### Admin API Managing content is done via Ghost’s Admin API, which has both read and write access used to create and update content. The Admin API provides secure role-based authentication so that you can publish from anywhere with confidence, either as a staff user via session authentication or via an integration with a third-party service. When authenticated with the **admin** or **owner** role, the Admin API provides full control for creating, editing and deleting all data in your publication, giving you even more power and flexibility than the standard Ghost admin client. ### JavaScript SDK Ghost core comes with an accompanying JavaScript [API Client](/docs/content-api/javascript/) and [SDK](/docs/content-api/javascript/#javascript-sdk) designed to remove pain around authentication and data access. It provides tools for working with API data to accomplish common use cases such as returning a list of tags for a post, rendering meta data in the ``, and outputting data with sensible fallbacks. Leveraging FLOSS & npm, an ever-increasing amount of Ghost’s JavaScript tooling has been made available. If you’re working in JavaScript, chances are you won’t need to code anything more than wiring. ### Webhooks Notify an external service when content has changed or been updated by calling a configured HTTP endpoint. This makes it a breeze to do things like trigger a rebuild in a static site generator, or notify Slack that something happened. By combining Webhooks and the API it is possible to integrate into any aspect of your content lifecycle, to enable a wide range of content distribution and workflow automation use cases. ### Versioning Ghost ships with a mature set of core APIs, with only minimal changes between major versions. We maintain a [stability index](/docs/faq/api-versioning/) so that you can be sure about depending on them in production. Ghost major versions ship every 8-12 months, meaning code you write against our API today will be stable for a minimum of 2 years. * * * Admin Client ------------ A streamlined clientside admin interface for editors who need a powerful tool to manage their content. Traditionally, people writing content and people writing code rarely agree on the best platform to use. Tools with great editors generally lack speed and extensibility, and speedy frameworks basically always sacrifice user experience. ### Overview Thanks to its decoupled architecture Ghost is able to have the best of both worlds. Ghost-Admin is a completely independent client application to the Ghost Core API which doesn’t have any impact on performance. And, writers don’t need to suffer their way through learning Git just to publish a new post. Great for editors. Great for developers. ![Ghost Admin](/images/docs/concepts/ghost-admin_hu71133877b7ba98790d6379dca0b417de_69599_1421x0_resize_q100_h2_box_3.webp) ### Publishing workflow Hacking together some Markdown files and throwing a static-site generator on top is nice in theory, but anyone who has tried to manage a content archive knows how quickly this falls apart even under light usage. What happens when you want to schedule a post to be published on Monday? ![Publishing Worfklow](/images/docs/concepts/publishing-workflow_hu46dc9229fc86b7d84b830ed2c4f822ed_23521_728x0_resize_q100_h2_box_3.webp) Great editorial teams need proper tools which help them be effective, which is why Ghost-Admin has all the standard editorial workflow features available at the click of a button. From inputting custom social and SEO data to customising exactly how and where content will be output. ### Best-in-class editor Ghost Admin also comes with a world-class editor for authoring posts, which is directly tied to a rock-solid document storage format. More on that a bit later! ![Ghost Editor](/images/docs/concepts/ghost-admin-editor_hu2424779d321f8c77f888150ff83e2859_59570_1236x0_resize_q100_h2_box_3.webp) But, our default client app isn’t the only way to interact with content on the Ghost [Admin API](/docs/admin-api/) . You can send data into Ghost from pretty much anywhere, or even write your own custom admin client if you have a particular usecase which requires it. Ghost-Admin is extremely powerful but entirely optional. * * * Front-end --------- Ghost is a full headless CMS which is completely agnostic of any particular front end or static site framework. Just like Ghost’s admin client, its front-end is both optional and interchangeable. While Ghost’s early architecture represented more of a standard monolithic web-app, it’s now compatible with just about any front-end you can throw at it. It doesn’t even have to be a website! ### Handlebars Themes Ghost ships with its own [Handlebars.js](/docs/themes/) theme layer served by an Express.js webserver, so out of the box it automatically comes with a default front-end. This is a really fast way to get a site up and running, and despite being relatively simple Handlebars is both powerful and extremely performant. Ghost Handlebars Themes have the additional benefit of being fairly widely adopted since the platform first launched back in 2013, so there’s a broad [third party marketplace](/marketplace/) of pre-built themes as well as [extensive documentation](/docs/themes/) on how to build a custom theme. ### Static Site Generators Thanks to its decoupled architecture Ghost is also compatible with just about any of the front-end frameworks or static site generators which have become increasingly popular thanks to being fun to work with, extremely fast, and more and more powerful as the JAMstack grows in maturity. So it works with the tools you already use. This very documentation site is running on a [Gatsby.js](/docs/jamstack/gatsby/) front-end, connected to both **Ghost** and **GitHub** as content sources, hosted statically on [Netlify](https://netlify.com) with dynamic serverless functions powered by [AWS Lambda](https://aws.amazon.com/lambda/) (like the feedback form at the bottom of this page). It’s a brave new world! We’re working on greatly expanding our range of documentation, tools and SDKs to better serve the wider front-end development community. ### Custom front-ends Of course you can also just build your own completely custom front-end, too. Particularly if you’re using the Ghost API as a service to drive content infrastructure for a mobile or native application which isn’t based on the web. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Learn how to update your self-hosted Ghost install to the latest version Our team [release](https://github.com/TryGhost/Ghost/releases) updates to the open source software every week, and you can find out whether new updates are available any time by running `ghost check-update`. If you’re already running the latest major version (`5.x`) - update using Ghost CLI with two commands: 1. Run `ghost backup` to generate a full backup of your site data 2. Then, run `ghost update` ✨ Updating to the latest major version ------------------------------------ If you’re not yet on the latest major version, there are two recommended methods for updating your Ghost install, depending on what version you’re currently on, and what type of database you’re using in production. Below is a full breakdown of the the recommended update paths. **[Updates](/docs/update-major-version/) are recommended for sites that are:** * Running Ghost version `3.0.0` or higher and are using MySQL in production * Development sites using any database **[A full reinstall](/docs/reinstall/) of Ghost is recommended for sites that are:** * Running on a Ghost version less than `3.0.0` * Using SQLite3 in production on any Ghost version | Ghost Version | Database | Update method | | --- | --- | --- | | 0.x | Any | [Reinstall](/docs/reinstall/) | | 1.x | Any | [Reinstall](/docs/reinstall/) | | 2.x | Any | [Reinstall](/docs/reinstall/) | | 3.x | SQLite | [Reinstall](/docs/reinstall/) | | 3.x | MySQL | [Update](/docs/update-major-version/) | | 4.x | SQLite | [Reinstall](/docs/reinstall/) | | 4.x | MySQL | [Update](/docs/update-major-version/) | _If you’re using MariaDB it is recommended to migrate to MySQL 8 - read more about [supported databases](/docs/faq/supported-databases/) ._ Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost product principles & roadmap Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Developing Ghost as a product is a complex process undertaken by a small number of people with a great deal of care. How we make product decisions ----------------------------- Ghost is a small, bootstrapped non-profit organization with no external funding. We make revenue from our [Ghost(Pro)](https://ghost.org/pricing/) platform, which sustains the company and funds a handful of developers who improve the software. Because we don’t have tens of millions of dollars in VC money or hundreds of developers, we have to carefully choose where to dedicate our limited team and resources. We can’t do everything. When deciding what to do next, we try to look at what would benefit most users, in most ways, most of the time. You can get a sense of those things over on [our public changelog](https://ghost.org/changelog/) . Outside of the core team, Ghost is completely [open source](https://github.com/tryghost/ghost/) , so anyone in the world can contribute and help build a feature that they’d like to see in the software, even if the core team isn’t working on it. Feature requests ---------------- We welcome feature requests from users over in [the ideas category](https://forum.ghost.org/c/Ideas) of the Ghost Forum. Here, people can request and suggest things which they’d like to see in Ghost, and others can add their votes. The ideas board is a great way for us to gauge user demand, but it’s not a democratic system. We don’t automatically build things just because they get a lot of votes, and not everything that gets requested makes it into core, but we do pay close attention to things with lots of demand. Why haven’t you built X yet? When will you? ------------------------------------------- Based on how we make product decisions, and what feature requests we get (detailed above) — if neither the core team nor the wider community are building the thing you want, then it’s likely that there isn’t enough demand or interest to make it happen at the moment. But, the beauty of open source is that if enough people want something, they can easily get together on GitHub and make it happen themselves (or fund someone else to). There’s no need to wait on the core team to deliver it. If you really want or need a particular feature, it’s entirely possible to make that happen. You just need to get involved, either with time and development skills, or with money to fund someone with time and development skills. I’m very upset you aren’t doing what I want! -------------------------------------------- For the most part, the Ghost community is kind, welcoming and very understanding about the complexities and constraints of building modern software. Every so often, though, we get a series of comments along the lines of: > Ya so, wow, I can’t believe this is broken and nobody is doing anything. How have you messed up something so basic? Can the devs fix ASAP. Thanks. Being hostile and entitled in your interactions with our community doesn’t make anybody feel excited about helping you. Not the core team, and certainly not the wider group of volunteer contributors. Try being friendly to people, and they’ll typically be friendly in return. If you feel really passionate about something specific, you have 3 potential courses of action: 1. Get involved on GitHub and contribute code to fix the issue 2. Hire a developer to get involved on GitHub and contribute code to fix the issue 3. Start a feature request topic on the forum to demonstrate that lots of other users care about this too, and have voted on it, which is the most likely way the core team will prioritize it. Is there a public roadmap for what’s coming next? ------------------------------------------------- The Ghost core team maintains a broad 1-2 year product roadmap at any given time which defines the overall direction of the company and the software. While the exact roadmap isn’t shared publicly (we tried it and it turned out to be more distracting than helpful), the things being worked on are generally very visible [on GitHub](https://github.com/tryghost/ghost) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to Migrate to Ghost from other platforms Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) [Ghost(Pro) migration services  →\ --------------------------------\ \ If you're a Ghost(Pro) customer, our team may be able to help you migrate your content and subscribers.](/concierge/) [![Substack](/images/docs/migration/substack-logo_huc24aa74ba24fa41c4541cf4ceebe96f7_2278_606x0_resize_q100_h2_box_3.webp)\ \ ### Substack](/docs/migration/substack/) [![beehiiv](/images/docs/migration/beehiiv-logo_hu4abad0e96a6745a35d2531e307809047_3360_117x0_resize_q100_h2_box_3.webp)\ \ ### BeeHiiv](/docs/migration/beehiiv/) [![Wordpress](/images/docs/migration/wordpress-logo.svg)\ \ ### WordPress](/docs/migration/wordpress/) [![Newspack](/images/docs/migration/newspack-logo_hu51abf2fafb510de653f416bb72277f36_8844_400x0_resize_q100_h2_box_2.webp)\ \ ### Newspack](/docs/migration/newspack/) [![Medium](/images/docs/migration/medium-logo_hu653fa3b1dcbd7dcc07a7092f13cb6eea_1762_225x0_resize_q100_h2_box_3.webp)\ \ ### Medium](/docs/migration/medium/) [![Squarespace](/images/docs/migration/squarespace-logo.svg)\ \ ### SquareSpace](/docs/migration/squarespace/) [![Kit](/images/docs/migration/kit-logo_hu3614f42c0366863f750e8a2729afab7f_6493_1147x0_resize_q100_h2_box_3.webp)\ \ ### Kit](/docs/migration/kit/) [![Mailchimp](/images/docs/migration/mailchimp-logo.svg)\ \ ### MailChimp](/docs/migration/mailchimp/) [![Patreon](/images/docs/migration/patreon-logo.svg)\ \ ### Patreon](/docs/migration/patreon/) [![Buttondown](/images/docs/migration/buttondown-logo_hucb227c87b3a9ce9bdbff00a32f010ee0_5514_400x0_resize_q100_h2_box_3.webp)\ \ ### Buttondown](/docs/migration/buttondown/) [![Memberful](/images/docs/migration/memberful-logo_hu1c13118575e70f224d3c44abd46f644f_10288_475x0_resize_q100_h2_box_3.webp)\ \ ### Memberful](/docs/migration/memberful/) [![Gumroad](/images/docs/migration/gumroad-logo_hud0833833547c6e5b6eb8af1cc6586779_22238_962x0_resize_q100_h2_box_3.webp)\ \ ### Gumroad](/docs/migration/gumroad/) [![Jekyll](/images/docs/migration/jekyll-logo.svg)\ \ ### Jekyll](/docs/migration/jekyll/) [![Ghost](/images/docs/migration/orb-black-1_hucb802f9049ed3348f8217d8dc62f8bad_51786_400x0_resize_q100_h2_box_3.webp)\ \ ### Ghost](/docs/migration/ghost/) [![Custom](/images/docs/migration/custom.svg)\ \ ### Other platforms](/docs/migration/custom/) Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Users & Permissions – Manage your team Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Staff users within Ghost have access to the admin area with varying levels of permissions for what they can do. Roles & permissions ------------------- There are five different staff user roles within Ghost * **Contributors:** Can log in and write posts, but cannot publish * **Authors:** Can create and publish new posts and tags * **Editors:** Can invite, manage and edit authors and contributors * **Administrators:** Have full permissions to edit all data and settings * **Owner:** An admin who cannot be deleted and has access to billing details Author archives --------------- Like [tags](/docs/publishing/#tags) , staff users are another resource by which content can be organised and sorted. Multiple authors can be assigned to any given post to generate bylines. Equally, author archives can be generated on the front end based on which posts an author is assigned to. Also like tags, within Ghost Handlebars Themes author archives are automatically added to the Google XML Sitemap, and have their own pagination + RSS feeds. Here’s an example of an [author archive](https://demo.ghost.io/author/martin/) in the default Ghost Theme: [![Author Archive](/images/docs/concepts/author-archive_hu89f6f73972391497d77bac6acdaa5b97_69300_1220x0_resize_q100_h2_box.webp)](https://demo.ghost.io/author/martin/) Public author archives are only generated for staff users who are assigned to published posts, any other staff users are not publicly visible. Security & trust ---------------- If running the front-end of your site and the Ghost admin client on the same domain, there are certain permissions escalation vectors which are unavoidable. Ghost considers staff users to be “trusted” by default - so if you’re running in an environment where users are untrusted, you should ensure that Ghost-Admin and your site’s front-end run on separate domains. Sample API data --------------- Here’s a sample author object from the Ghost [Content API](/docs/content-api/) { "authors": [\ {\ "slug": "cameron",\ "id": "5ddc9b9510d8970038255d02",\ "name": "Cameron Almeida",\ "profile_image": "https://docs.ghost.io/content/images/2019/03/1c2f492a-a5d0-4d2d-b350-cdcdebc7e413.jpg",\ "cover_image": null,\ "bio": "Editor at large.",\ "website": "https://example.com",\ "location": "Cape Town",\ "facebook": "example",\ "twitter": "@example",\ "meta_title": null,\ "meta_description": null,\ "url": "https://docs.ghost.io/author/cameron/"\ }\ ] } Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Memberships – Ghost Developer Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) The native Members feature in Ghost makes it possible to launch a membership business from any Ghost publication, with member signup, paid subscriptions and email newsletters built-in. Overview -------- Any publisher who wants to offer a way for their audience to support their work can use the Members feature to share content, build an audience, and generate an income from a membership business. ![Member settings in Ghost Admin](/images/docs/members/ghost-sites_hud78661df9259815cb29707ecbfccff73_228853_1500x0_resize_q100_h2_box_3.webp) The concepts and components that enable you to turn a Ghost site into a members publication are surprisingly simple and can be broken down into two concepts: 1\. Memberships --------------- A member of a Ghost site is someone who has opted to subscribe, and confirmed their subscription by clicking the link sent to their inbox. Members are stored in Ghost, to make tracking, managing and supporting an audience a breeze. ### Secure authentication Ghost uses passwordless JWT email-link based logins for your members. It’s fast, reliable, and incredible for security. Secure email authentication is used for both member sign up and sign in. ### Access levels Once a visitor has entered their email address and confirmed membership, you can share protected content with them on your Ghost publication. Logged in members are able to access any content that matches their tier. The following access levels are available to select from the post settings in the editor: * **Public** * **Members only** * **Paid-members only** * **Specific tier(s)** Content is securely protected at server level and there is no way to circumvent gated content without being a logged-in member. ### Managing members Members are stored in Ghost with the following attributes: * `email` (required) * `name` * `note` * `subscribed_to_emails` * `stripe_customer_id` * `status` (free/paid/complimentary) * `labels` * `created_at` ### Imports It’s possible to import Members from any other platform. If you have a list of email addresses, this can be ported into Ghost via CSV, Zapier, or the API. 2\. Subscriptions ----------------- Members in Ghost can be free members, or become paid members with a direct Stripe integration for fast, global payments. ### Connect to Stripe We’ve built a direct [integration with Stripe](/integrations/stripe/) which allows publishers to connect their Ghost site to their own billing account using Stripe Connect. ![Member settings in Ghost Admin](/images/docs/members/connect-to-stripe_hu44a97624d38f04e23e06510778716a3f_39290_1906x0_resize_q100_h2_box_3.webp) Payments are handled by Stripe and billing information is stored securely inside your own Stripe account. ### Transaction fees Ghost takes **0%** of your revenue. Whatever you generate from a paid blog, newsletter or community is yours to keep. Standard Stripe processing fees still apply. ### Portability All membership, customer and business data is controlled by you. Your members list can be exported any time and since subscriptions and billing takes place inside your own Stripe account, you retain full ownership of it. If you’re migrating an existing membership business from another platform, check our our [migration docs](/docs/migration/) . ### Alternative payment gateways To begin with, Stripe is the only natively supported payment provider with Ghost. We’re aware that not everyone has access to Stripe, and we plan to add further payment providers in future. In the meantime, it is possible to create new members via an external provider, such as [Patreon](/integrations/patreon/) or [PayPal](/integrations/paypal/) . You can set up any third party payments system and create members in Ghost via API, or using automation tools like Zapier. ### I have ideas / suggestions / problems / feedback Great! We set up a dedicated [forum category](https://forum.ghost.org/c/members) for feedback about the members feature, we appreciate your input! We’re continuously shipping improvements and new features at Ghost which you can follow over on [GitHub](https://github.com/tryghost/ghost) , or on our [Changelog](/changelog/) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Email Newsletters — Ghost Developer Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Sites using the Members feature benefit from built-in email newsletters, where all posts can be delivered directly to segments of your audience in just a few clicks. Overview -------- Email newsletters in Ghost can be scheduled and delivered to free and paid members, or a segment of free _or_ paid members. Newsletters are delivered using a beautiful HTML template that is standardised for most popular email clients. Ghost sites have a single newsletter by default but additional ones can be created and customised. Multiple newsletters allow you to tailor content for specific audiences and your members to choose which content they receive. Bulk email configuration ------------------------ In order to send email newsletters from a Ghost site, email needs to be configured. ### Ghost(Pro) When using [Ghost(Pro)](/pricing/) , email delivery is included and the configuration is handled for you automatically. ### Self-hosted Self-hosted Ghost installs can configure bulk email by entering Mailgun API keys from the **Email newsletter** settings. Delivering bulk email newsletters can’t be done with basic SMTP. A bulk mail provider is a requirement to reliably deliver bulk mail. At present, Mailgun is the only supported bulk email provider. Mailgun is free for up to 600 emails per month, and has very reasonable pricing beyond that. [More info here](/docs/faq/mailgun-newsletters/) ![Configure email newsletters with Mailgun API keys](/images/docs/members/configure-email-mailgun_huadcebd46ebc845ccc560c91272939bbb_16705_1770x0_resize_q100_h2_box_3.webp) ### Auth email The Members feature uses passwordless email-link based logins for your members. These auth emails are not delivered in bulk and are sent using the standard mail configuration in Ghost. Self-hosted Ghost installs can [configure mail](/docs/config/#mail) using Mailgun or other providers if preferred. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Security & Privacy Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost is committed to developing secure, reliable products utilising all modern security best practices and processes. The Ghost team is made up of full time staff employed by the Ghost Foundation as well as volunteer open source contributors and security experts. We do both consultation and penetration testing of our software and infrastructure with external security researchers and agencies. We take security seriously at Ghost and welcome any peer review of our [open source codebase](https://github.com/tryghost/ghost) to help ensure that it remains secure. Security features ----------------- #### Device verification All staff user login sessions from a new or unrecognized device must be verified with a code sent to the user’s registered email address. #### Email 2FA Ghost can be configured to send two-factor authentication codes by email on all staff user logins. #### Brute force protection User login attempts and password reset requests are all limited to 5 per hour per IP address. #### Automatic SSL Ghost’s CLI tool automatically configures SSL certificates for all new Ghost installs with Let’s Encrypt by default. #### Password hashing Ghost follows [OWASP authentication standards](https://www.owasp.org/index.php/Top_10-2017_A2-Broken_Authentication) with all passwords hashed and salted properly using `bcrypt` to ensure password integrity. #### Encoded tokens everywhere All user invitation and password reset tokens are base64 encoded with serverside secret. All tokens are always single use and always expire. #### SQLi prevention Ghost uses [Bookshelf](https://bookshelfjs.org/) ORM + [Knex](https://knexjs.org) query builder and does not generate _any_ of its own raw SQL queries. Ghost has no interpolation of variables directly to SQL strings. #### Data validation and serialisation Ghost performs strong serialisation and validation on all data that goes into the database, as well as automated symlink protection on all uploaded files. #### XSS prevention Ghost uses safe/escaped strings used everywhere, including and especially in all custom Handlebars helpers used in [Ghost Themes](/docs/themes/) #### Standardised permissions Ghost-CLI does not run as `root` and automatically configures all server directory permissions correctly according to [OWASP Standards](https://www.owasp.org/index.php/File_System) . #### Dependency management All Ghost dependencies are continually scanned using a combination of automated GitHub tooling and `yarn audit` to ensure their integrity. * * * Reporting vulnerabilities ------------------------- Potential security vulnerabilities can be reported directly to us at `security@ghost.org`. The Ghost Security Team communicates privately and works in a secured, isolated repository for tracking, testing, and resolving security-related issues. ### Responsible disclosure The Ghost Security team is committed to working with security researchers to verify, reproduce and respond to legitimate reported vulnerabilities. * Provide details of the vulnerability, including information needed to reproduce and validate the vulnerability and a Proof of Concept * Make a good faith effort to avoid privacy violations, destruction and modification of data on live sites * Give reasonable time to correct the issue before making any information public Security issues always take precedence over bug fixes and feature work. We can and do mark releases as “urgent” if they contain serious security fixes. We will publicly acknowledge any report that results in a security commit to [https://github.com/TryGhost/Ghost](https://github.com/TryGhost/Ghost) ### Issue triage We’re always interested in hearing about any reproducible vulnerability that affects the security of Ghost users, including… * Remote Code Execution (RCE) * SQL Injection (SQLi) * Server Side Request Forgery (SSRF) * Cross Site Request Forgery (CSRF) * Cross Site Scripting (XSS) but please read on before reporting XSS… ##### However, we’re generally _not_ interested in… * [Privilege escalation](#privilege-escalation-attacks) as result of trusted users publishing arbitrary JavaScript[1](#privilege-escalation-attacks) * HTTP sniffing or HTTP tampering exploits * Open API endpoints serving public data * Ghost version number disclosure * Brute force, DoS, DDoS, phishing, text injection, or social engineering attacks. * Output from automated scans * Clickjacking with minimal security implications * Missing DMARC records ##### Privilege escalation attacks Ghost is a content management system and all users are considered to be privileged/trusted. A user can only obtain an account and start creating content after they have been invited by the site owner or similar administrator-level user. A basic feature of Ghost as a CMS is to allow content creators to make use of scripts, SVGs, embedded content & other file uploads that are required for the content to display as intended. Because of this there will always be the possibility of “XSS” attacks, albeit only from users that have been trusted to build the site’s content. Ghost’s admin application does a lot to ensure that unknown scripts are not run within the the admin application itself, however that only protects one side of a Ghost site. If the front-end (the rendered site that anonymous visitors see) shares the same domain as the admin application then browsers do not offer sufficient protections to prevent successful XSS attacks by trusted users. If you are concerned that trusted users you invite to create your site will act maliciously the best advice is to split your front-end and admin area onto different domains (e.g. `https://mysite.com` and `https://admin.mysite.com/ghost/`). This way browsers offer greater built-in protection because credentials cannot be read across domains. Even in this case it should be understood that you are giving invited users completely free reign in content creation so absolute security guarantees do not exist. Anyone concerned about the security of their Ghost install should read our [hardening guide](/docs/hosting/#server-hardening) . We take any attack vector where an untrusted user is able to inject malicious content very seriously and welcome any and all reports. ### How reports are handled If you report a vulnerability to us through the [security@ghost.org](mailto:security@ghost.org) mailing list, we will: * Acknowledge your email within a week * Investigate and let you know our findings within two weeks * Ensure any critical issues are resolved within a month * Ensure any low-priority issues are resolved within three months * Credit any open source commits to you * Let you know when we have released fixes for issues you report * * * Privacy ------- Ghost as an organisation is self-funded, wholly independent, and only makes revenue directly from its customers. It has zero business interests of any kind predicated on selling private user data. In addition the Ghost software itself contains [a plainly written summary](https://github.com/TryGhost/Ghost/blob/3d989eba2371235d41468f7699a08e46fc2b1e87/PRIVACY.md) of every privacy-affecting feature within Ghost, along with detailed configuration options allowing any and all of them to be disabled at will. We take user privacy seriously. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Publishing Core Concepts – Ghost Developer Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Posts are the primary entry-type within Ghost, and generally represent the majority of stored data. By default Ghost will return a reverse chronological feed of posts in the traditional format of a blog. However, a great deal of customisation is available for this behaviour. Overview -------- Posts are created within Ghost-Admin using the editor to determine your site’s main content. Within them are all the fields which you might expect such as title, description, slug, metadata, authors, tags and so on. Additionally, posts have **Code Injection** fields which mean you can register additional styles, scripts or other content to be injected just before `` or `` on any one particular URL where desired. Here’s an example of a [post](https://demo.ghost.io/welcome/) in the default Ghost Theme: [![Post in the default Ghost Theme](/images/docs/concepts/post_hue747db7f2f90c2b84166e8bb760c8a59_946454_3014x0_resize_q100_h2_box_3.webp)](https://demo.ghost.io/welcome/) Creating content ---------------- Creating content in Ghost is done via the Ghost editor which, for many people, is what attracted to them in the first place. More than just a glossy experience though, Ghost’s editor provides a streamlined workflow for both authors and developers. ### Writing experience The writing experience in Ghost will be very familiar to most people who have spent time with web based authoring tools. It generally takes after the Medium-like experience which writers want. Writing simple content is a breeze - but there are tons of powerful shortcuts, too. You can write plaintext, activating formatting options using either the mouse or keyboard shortcuts. But you can also write in Markdown, if you prefer, and the editor will convert it as you type - rendering an instant preview. ![Ghost editor in action](/images/docs/concepts/editor_hudded6a316f9fdf6cb4455db6918ad2d9_121582_1806x0_resize_q100_h2_box_3.webp) Additionally, the editor contains intelligent logic around pasting. You can copy and paste from _most_ sources and it will be correctly transformed into readable content without needing any special treatment. (Go ahead, try copying the content of this page straight into the editor!) — You can also do things like pasting a URL over the top of any highlighted text to create a link. ### Dynamic cards Having a clean writing experience is good, but nowadays great publishing means so much more than just text. Modern content contains audio, video, charts, data and interactive elements to provide an engaging experience. Ghost content comes with extensible, rich media objects called Cards. The easiest way to think of them is like having Slack integrations in your content. ![Cards](/images/docs/concepts/cards_hu530bde54718f2b313fb590dbef31cd87_116580_932x0_resize_q100_h2_box_3.webp) **For example:** Either by pressing the `+` button or typing `/` - you can trigger an Unsplash integration to find and insert a royalty-free photo for your post. _Currently there are only a few simple cards available, but greater support for cards (as well as support for custom cards) is in active development._ ### Document storage The Ghost editor gets a lot of praise from writers for being a pleasure to use, but developers will find that the standardised JSON-based document storage format under the hood creates an equally great experience when it comes to working with the data. All post content in Ghost is stored in [Lexical](https://lexical.dev) and then rendered into its final form depending on the delivery destination. Lexical is extremely portable and can be transformed into multiple formats. This is particularly powerful because it’s just as easy to parse your content into HTML to render on the web as it is to pull the same content into a mobile app using completely different syntax. ### API data Here’s a sample post object from the Ghost [Content API](/docs/content-api/) { "posts": [\ {\ "slug": "welcome-short",\ "id": "5ddc9141c35e7700383b2937",\ "uuid": "a5aa9bd8-ea31-415c-b452-3040dae1e730",\ "title": "Welcome",\ "html": "

👋 Welcome, it's great to have you here.

",\ "comment_id": "5ddc9141c35e7700383b2937",\ "feature_image": "https://static.ghost.org/v3.0.0/images/welcome-to-ghost.png",\ "feature_image_alt": null,\ "feature_image_caption": null,\ "featured": false,\ "visibility": "public",\ "created_at": "2019-11-26T02:43:13.000+00:00",\ "updated_at": "2019-11-26T02:44:17.000+00:00",\ "published_at": "2019-11-26T02:44:17.000+00:00",\ "custom_excerpt": null,\ "codeinjection_head": null,\ "codeinjection_foot": null,\ "custom_template": null,\ "canonical_url": null,\ "url": "https://docs.ghost.io/welcome-short/",\ "excerpt": "👋 Welcome, it's great to have you here.",\ "reading_time": 0,\ "access": true,\ "og_image": null,\ "og_title": null,\ "og_description": null,\ "twitter_image": null,\ "twitter_title": null,\ "twitter_description": null,\ "meta_title": null,\ "meta_description": null,\ "email_subject": null\ }\ ] } Pages ----- Pages are a subset of posts which are excluded from all feeds. While posts are used for grouped content which is generally published regularly like blog posts or podcast episodes, pages serve as a separate entity for static and generally independent content like an `About` or `Contact` page. ### What’s different about pages? Pages are only ever published on the slug which is given to them, and do not automatically appear anywhere on your site. While posts are displayed in the index collection, within RSS feeds, and in author and tag archives - pages are totally independent. The only way people find them is if you create manual links to them either in your content or your navigation. Here’s an example of a [page](https://demo.ghost.io/about/) in the default Ghost Theme: [![Page](/images/docs/concepts/page_hue747db7f2f90c2b84166e8bb760c8a59_805758_3014x0_resize_q100_h2_box_3.webp)](https://demo.ghost.io/about/) Custom templates ---------------- If using one of Ghost’s default [Handlebars Themes](/docs/themes/) , a common usecase for pages is to give them custom templates. As well as a regular `page.hbs` default template, you can also create generic reusable custom templates like `page-wide.hbs` - or page-specific templates based on a particular slug, like `page-about.hbs` - so that you have fine-grained control over what markup is used to render your data. Not much else to say about pages, let’s move right along. Tags ---- Tags are the primary taxonomy within Ghost for filtering and organising the relationships between your content. Right off the bat, probably the best way to think about tags in Ghost is like labels in GMail. Tags are a powerful, dynamic taxonomy which can be used to categorise content, control design, and drive automation within your site. Tags are much more than just simple keywords - there are several different ways of using them to accomplish a variety of use-cases. ### Regular tag All tags come with their own data object and can have a title, description, image and meta data. Ghost Handlebars Themes will automatically generate tag archive pages for any tags which are assigned to active posts. For example all posts tagged with `News` will appear on `example.com/tag/news/`, as well as in the automatically generated XML sitemap. ### Primary tag Ghost has a concept of `primary_tag`, used simply to refer to the very first tag which a post has. This is useful for when you want to return a singular, most-important tag rather than a full array of all tags assigned to a post. ### Internal tag Tags which are prefixed by a `#` character, otherwise known as hashtags, are internal tags within Ghost - which is to say that they aren’t rendered publicly. This can be particularly useful when you want to drive particular functionality based on a tag, but you don’t necessarily want to output the tag for readers to see. ### Example usage As a quick example of how you might use tags, let’s look at a quick example of a Hollywood news site which is publishing a post about Ryan Reynolds being announced as the lead in a new movie called “Son of Deadpool”. ![Tags](/images/docs/concepts/tags_hud43c01f8e89efe2aea0eb402d3bb391f_81753_1648x0_resize_q100_h2_box_3.webp) Here the post has 4 tags: * `Breaking news` - The **primary tag** * `Ryan Reynolds` - A regular tag * `New Releases` - A regular tag * `#feature` - An internal tag The front-end of the site has configured a rotating banner on the homepage to pull the latest 3 posts from the `Breaking News` category and highlight them right at the top of the page with a **Breaking News** label beside the byline. The `Ryan Reynolds` and `New Releases` tags generate archives so that readers can browse other stories in the same categories, as well as their own sitemaps. The `#feature` tag is used by the front-end or theme-layer as a conditional flag for activating specific formatting. In this instance the Deadpool PR team have supplied some marketing material including a giant wallpaper image which would make a great background, so the post is tagged with `#feature` to push the post image to be full bleed and take over the whole page. You can see this use-case in action on the main Ghost blog. Here’s [a regular post](/changelog/image-galleries/) , and here’s a [#feature](/changelog/5/) . The design of the post reacts to the tags. Tag archives ------------ All actively used public tags (so, those not prefixed with `#`) generate automatic tag archives within Ghost Handlebars Themes. Tag archives are automatically added to the Google XML Sitemap, and have their own pagination and RSS feeds. Here’s an example of an [tag archive](https://demo.ghost.io/tag/getting-started/) in the default Ghost Theme: [![Tag Archive](/images/docs/concepts/tag-archive_hue747db7f2f90c2b84166e8bb760c8a59_994594_3014x0_resize_q100_h2_box_3.webp)](https://demo.ghost.io/tag/getting-started/) Tag archives are only generated for tags which are assigned to published posts, any other tags are not publicly visible. ### API data Here’s a sample tag object from the Ghost [Content API](/docs/content-api/) : { "tags": [\ {\ "slug": "getting-started",\ "id": "5ddc9063c35e7700383b27e0",\ "name": "Getting Started",\ "description": null,\ "feature_image": null,\ "visibility": "public",\ "meta_title": null,\ "meta_description": null,\ "og_image": null,\ "og_title": null,\ "og_description": null,\ "twitter_image": null,\ "twitter_title": null,\ "twitter_description": null,\ "codeinjection_head": null,\ "codeinjection_foot": null,\ "canonical_url": null,\ "accent_color": null,\ "url": "https://docs.ghost.io/tag/getting-started/"\ }\ ] } Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to use responsive images in Ghost themes Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost themes support automatic image resizing, allowing you to use a minimal handlebars helper to output different image sizes. Ghost automatically compresses and resizes images added to your post content and generates automatic responsive assets for maximum performance. For all other images, such as feature images and theme images, the responsive images feature builds responsive image srcsets into your theme, and displays scaled down images when required to improve your site’s overall performance. Responsive images configuration ------------------------------- Responsive images can be defined in the `package.json` file. Ghost automatically generates copies of images at the specified sizes, and works like a cache, so the image sizes can be changed at any time. It’s recommended to have no more than 10 image sizes so media storage doesn’t grow out of control. Here’s a sample of [the image sizes in Ghost’s default Casper theme](https://github.com/TryGhost/Casper/blob/main/package.json) . // package.json "config": { "image_sizes": { "xxs": { "width": 30 }, "xs": { "width": 100 }, "s": { "width": 300 }, "m": { "width": 600 }, "l": { "width": 1000 }, "xl": { "width": 2000 } } } ### Using image sizes Once image sizes are defined, pass a `size` parameter to the [{{img\_url}}](/docs/themes/helpers/img_url/) helper in your theme to output an image at a particular size. To build [full responsive images](https://medium.freecodecamp.org/a-guide-to-responsive-images-with-ready-to-use-templates-c400bd65c433) create html srcsets passing in multiple image sizes, and let the browser do the rest. Here’s an [example from Ghost default Casper theme](https://github.com/TryGhost/Casper/blob/main/partials/post-card.hbs) implementation: {{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}} ### Converting images to smaller image types Pass a `format` parameter to the [{{img\_url}}](/docs/themes/helpers/img_url/) helper in your theme to output an image in a particular image format. This only works in combination with the `size` parameter. {{img_url feature_image size="s" format="webp"}} By converting an image from PNG, GIF, or JPEG to WebP, you can reduce its size by ~25% without any visible loss of quality. An even better image compression can be obtained with the AVIF format, but this [isn’t supported in all browsers](https://caniuse.com/avif) (and doesn’t support animation yet). _Note that while image conversion changes the file type, the file extension stays the same. For example, an AVIF image will retain the `.jpg` extension._ WebP is supported by all modern browsers, but we recommend to always add a fallback to the original file type to achieve wider browser support. Use a `` tag for this, which allows the browser to choose the first format it supports: {{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}} Compatibility ------------- Unlike other platforms, there’s no manual work needed to manage image sizes in your theme, it’s all done in the background for you. Image sizes are automatically generated for all feature images and theme images, and regenerated whenever an image is changed, the image sizes configuration is changed, or when theme changes are made. Images are generated on the first request for each image at a particular size. Dynamic image sizes are _not_ compatible with externally hosted images (except inserted images from [Unsplash](/integrations/unsplash/) ). If you store your image files on a third party storage adapter, then the image URL returned will be determined by the external source. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Theme Features: The Editor - Documentation Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) The open-source Ghost editor is robust and extensible. More than just a formatting toolbar, the rich editing experience within Ghost allows authors to pull in dynamic blocks of content like photos, videos, tweets, embeds, code and markdown. For author-specified options to work, themes need to support the HTML markup and CSS classes that are output by the `{{content}}` helper. Use the following examples to ensure your theme is compatible with the latest version of the Ghost editor. `
` and `
` ----------------------------- Images and embeds will be output using the semantic `
` and `
` elements. For example: {{/* Output */}}
An example image
The following CSS classes are used: * `.kg-image-card` is used on the `
` element for all image cards * `.kg-image` is used on the `` element for all image cards * `.kg-embed-card` is used on the `
` element on all embed cards This is only relevant when authors use the built-in image and embed cards, and themes must also support images and embeds that are not wrapped in `
` elements to maintain compatibility with the Markdown and HTML cards. Image size options ------------------ The editor allows three size options for images: normal, wide and full width. These size options are achieved by adding `kg-width-wide` and `kg-width-full` classes to the `
` elements in the HTML output. Here’s an example for wide images: {{/* Output */}}
Normal width image cards do not have any extra CSS classes. Image cards have `width` and `height` attributes when that data is available. Width and height correspond to the size and aspect ratio of the source image and do not change when selecting different size options in the editor. _If your theme has a `max-width` style set for images it’s important to also have `height: auto` to avoid images appearing stretched or squashed._ The specific implementation required for making images wider than their container width will depend on your theme’s existing styles. The default Ghost theme Casper uses flexbox to implement layout using the following HTML and CSS:

Image size implementation

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce at interdum ipsum.

A full-width image

Fusce interdum velit tristique, scelerisque libero et, venenatis nisi. Maecenas euismod luctus neque nec finibus.

A wide image

Suspendisse sed lacus efficitur, euismod nisi a, sollicitudin orci.

An example post
And the CSS: /* style.css */ .content { width: 70%; margin: 0 auto; } article { display: flex; flex-direction: column; align-items: center; } article img { display: block; max-width: 100%; height: auto; } .kg-width-wide img { max-width: 85vw; } .kg-width-full img { max-width: 100vw; } article figure { margin: 0; } article figcaption { text-align: center; } body { margin: 0; } header, footer { padding: 15px 25px; background-color: #000; color: #fff; } h1 { width: 100%; } ### Negative margin and transforms example Traditional CSS layout doesn’t support many elegant methods for breaking elements out of their container. The following example uses negative margins and transforms to achieve breakout. Themes that are based on Casper use similar techniques. /* style.css */ .content { width: 70%; margin: 0 auto; } article img { display: block; max-width: 100%; height: auto; } .kg-width-wide { position: relative; width: 85vw; min-width: 100%; margin: auto calc(50% - 50vw); transform: translateX(calc(50vw - 50%)); } .kg-width-full { position: relative; width: 100vw; left: 50%; right: 50%; margin-left: -50vw; margin-right: -50vw; } article figure { margin: 0; } article figcaption { text-align: center; } body { margin: 0; } header, footer { padding: 15px 25px; background-color: #000; color: #fff; } ### Responsive image sizes Where possible images will have `srcset` and `sizes` attributes to allow for smaller images to be served to devices with smaller screens. Full output will look similar to this: {{/* Output */}}
A rugged coastline with small groups of people walking around rock pools
Editor cards ------------ Each of the content cards available in the editor require CSS and Javascript to display and function correctly. These default CSS and Javascript assets are provided automatically by Ghost, and output as `cards.min.css` and `cards.min.js` in the `{{ghost_head}}` helper. You can override the default styles and behaviour for individual cards by configuring your theme’s `package.json` to exclude the assets for specific cards: "card_assets": { "exclude": ["bookmark", "gallery"] } Alternatively you can disable all cards, by setting `card_assets` to false (the default is true). "card_assets": false The available cards are `audio`, `blockquote`, `bookmark`, `button`, `callout`, `file`, `gallery`, `header`, `nft`, `product`, `toggle`, `video`, and `signup`. You can customize the styles of individual cards by using custom CSS. Each card has a unique class name that you can target to apply your own styles. Here’s a list of the class names for each card type: * Audio: `.kg-audio-card` * Blockquote: `blockquote` or `.kg-blockquote-alt` * Bookmark: `.kg-bookmark-card` * Button: `.kg-button-card` * Callout: `.kg-callout-card` * File: `.kg-file-card` * Gallery: `.kg-gallery-card` * Header: `.kg-header-card` * NFT: `.kg-nft-card` * Product: `.kg-product-card` * Toggle: `.kg-toggle-card` * Video: `.kg-video-card` * Signup: `.kg-signup-card` .kg-product-card .kg-product-card-container { background-color: #f0f0f0; } ### Gallery card The image gallery card requires some CSS and JS in your theme to function correctly. Themes will be validated to ensure they have styles for the gallery markup: * `.kg-gallery-container` * `.kg-gallery-row` * `.kg-gallery-image` Example gallery HTML: {{/* Output */}} For a better view of how to support the gallery card in your theme, use the default implementation of the [CSS](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/gallery.css) and [Javascript](https://github.com/TryGhost/Ghost/blob/3d989eba2371235d41468f7699a08e46fc2b1e87/ghost/core/core/frontend/src/cards/js/gallery.js) assets provided by Ghost, which is a generic solution that works for most themes. ### Bookmark card Here’s an example of the HTML structure that’s created by the editor: {{/* Output */}}
The bookmark card
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce at interdum ipsum.
The default CSS for the bookmark card [provided by Ghost](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/bookmark.css) should be used as a reference for custom implementations. ### Embed card If a video is used with the theme then some CSS will be needed in order to maintain a good aspect ratio. Example HTML:
The CSS: .fluid-width-video-wrapper { position: relative; overflow: hidden; padding-top: 56.25%; } .fluid-width-video-wrapper iframe, .fluid-width-video-wrapper object, .fluid-width-video-wrapper embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } ### NFT card NFT embeds are provided by [OpenSea](https://opensea.io) . Example HTML:
The default CSS for the NFT card [provided by Ghost](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/nft.css) should be used as a reference for custom implementations. ### Button card Button cards insert a link that is styled like a button using the site’s configured accent color and can be left or center aligned. Example HTML: The default CSS for the button card [provided by Ghost](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/button.css) should be used as a reference for custom implementations. ### Callout card Callout cards show a highlighted box with an emoji and a paragraph of text. Example HTML:
💡
Did you know about the callout card?
The default CSS for the callout card [provided by Ghost](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/callout.css) should be used as a reference for custom implementations. ### Toggle card Toggle cards show a collapsible content box with heading and arrow icon. Example HTML:

Do you give any discounts ?

Yes, we give 20% off on annual subscriptions.
The default CSS for the toggle card [provided by Ghost](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/toggle.css) should be used as a reference for custom implementations. ### Alternative blockquote style There are two styles of blockquote available that can by cycled through by repeatedly pressing the blockquote toolbar icon. Example HTML:
Standard blockquote style
Alternative blockquote style
The default CSS for the alternative style [provided by Ghost](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/blockquote.css) should be used as a reference for custom implementations. ### Audio upload card Audio card allows uploading custom audio files. Example HTML:
audio-thumbnail
File example MP3
0:00
/2:12
The default [CSS](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/audio.css) and [Javascript](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/js/audio.js) for the audio card provided by Ghost should be used as a reference for custom implementations. ### Video upload card Video card allows uploading custom video files. Example HTML:
0:00
/
The default [CSS](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/video.css) and [Javascript](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/js/video.js) for the video card provided by Ghost should be used as a reference for custom implementations. ### File upload card File card allows uploading custom files for download. Example HTML: The default [CSS](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/file.css) for the file card provided by Ghost should be used as a reference for custom implementations. ### Header card The header card gives you the ability to add headers to your posts and pages. Example HTML:

Header

Subheader

Button Text
The main card can have a `kg-size-` class of either: `kg-size-small`, `kg-size-medium` or `kg-size-large` and a `kg-style-` class of either `kg-style-dark`, `kg-style-light`, `kg-style-accent, or` kg-style-image\`. The default [CSS](https://github.com/TryGhost/Ghost/blob/c667620d8f2e32c96fe376ad0f3dabc79488532a/ghost/core/core/frontend/src/cards/css/header.css) for the card can be used as a reference implementation. ### Signup card The signup card adds a customizable signup form to posts. (Only available in the [new beta editor](https://ghost.org/changelog/editor-beta/) .) For `kg-width-`, `size` can be `kg-width-regular`, `kg-width-wide`, or `kg-width-full`. Full-width and split-layout with contained image cards provide a `kg-content-wide` class. Use this class to ensure card content is properly positioned and sized. See [Casper’s implementation](https://github.com/TryGhost/Casper/blob/2fafe722d1ee997f5f1b597de859fe2462090e42/assets/css/screen.css#L1298-L1312) for a guide. Split-layout signup cards, which include an image adjacent to the text content, provide the `kg-layout-split` class. See the [default CSS](https://github.com/TryGhost/Ghost/blob/4c72f4567600a59a64be10f38acf851bffaa6dec/ghost/core/core/frontend/src/cards/css/signup.css) included with this card. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Recommendations – Ghost Developer Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Overview -------- With recommendations, publishers can share their favorite sites with their readers and, likewise, be recommended by other publications. See [the Changelog](https://ghost.org/changelog/recommendations/) for an overview of this feature. ![Recommendations in Ghost](/images/docs/recommendations_hu87dd64b769c4fd4ef600cc9c9bc971ce_570214_2000x0_resize_q100_h2_box_3.webp) Under the hood, Ghost’s recommendations feature is built on the [Webmention open standard](https://www.w3.org/TR/webmention/) , which means recommendations aren’t limited to any single platform — but extend to every site on the web! Recommendations also make it possible for readers to subscribe to recommended publications with a single click. While this feature is currently exclusive to Ghost sites, we are eager to help other platforms in implementing this 1-click functionality. Contact us if you’re interested in building 1-click subscriptions for the open web! The sections below provide a high-level technical summary of how recommendations work. See your site’s recommendations ------------------------------- * The recommendations modal is shown automatically whenever a new member subscribes to a Ghost publication. * Visiting `https://yoursite.com/#/portal/recommendations` will open the recommendations modal. Use this URL as a link in the navigation menu to create a recommendation button. * See additional methods for opening the recommendations modal in our [theme docs](/docs/themes/helpers/recommendations/) . How Ghost sends a recommendation -------------------------------- When you make a recommendation, it shows on your website and in Portal at `yoursite.com/#/portal/recommendations`. Behind the scenes, Ghost performs the following steps: 1. Ghost checks to see if the recommended site has Webmentions enabled. While it’s possible to recommend any site, Ghost only notifies sites about your recommendation if they have a Webmention endpoint that can receive it. 2. Ghost adds the recommendation to your site’s `/.well-known/recommendations.json` file. Here’s an example of this file: [\ {\ "url": "https://shesabeast.co/",\ "updated_at": "2023-09-22T14:09:32.000Z",\ "created_at": "2023-09-22T14:09:32.000Z"\ },\ {\ "url": "https://makerstations.io/",\ "updated_at": "2023-09-22T14:12:40.000Z",\ "created_at": "2023-09-22T14:12:34.000Z"\ }\ ] 3. Ghost notifies the recommended site via a Webmention. This takes the form of a POST request to the endpoint discovered in step 1 and contains a reference to your site’s `recommendations.json` file. Here’s an example of a request: POST /webmentions/receive/ HTTP/1.1 Host: recommendedsite.com Content-Type: application/x-www-form-urlencoded source=https://mysite.com/.well-known/recommendations.json& target=https://recommendedsite.com/ HTTP/1.1 202 Accepted How Ghost receives a recommendation ----------------------------------- Your site receives recommendations in the same way as described above but as the recipient. 1. Ghost automatically adds a `link` tag to your publication to inform other sites about your Webmention endpoint. That tag looks like this: 2. Ghost listens for Webmentions on this endpoint. Once the incoming recommendation is verified, it’s added to Ghost Admin and you receive a notification. Updates and removals -------------------- If you update or remove a recommended site, Ghost sends an updated Webmention about the change. Likewise, your site will be automatically updated whenever it receives an incoming recommendation change. Theme support ------------- Theme developers can extend the recommendation feature by using the [`recommendations`](/docs/themes/helpers/recommendations/) and [`readable_url`](/docs/themes/helpers/readable_url/) helpers. See the documentation for these features to learn more. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Context Overview: Ghost Themes - Documentation Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Each page in a Ghost theme belongs to a context, which determines which template is used, what data will be available and what content is output by the `{{body_class}}` helper. A Ghost publication follows a structure that allows URLs or routes to be mapped to views which display specific data. This data could be a list of posts, a single post or an RSS feed. It is the route that determines what data is meant to be shown and what template is used to render it. Rather than providing access to all data in all contexts, Ghost optimises what data is fetched using contexts to ensure publications are super fast! ### Using contexts Contexts play a big part in the building blocks of a Ghost theme. Besides determining what data is available and what template to render, contexts also interact with [handlebars helpers](/docs/themes/helpers/) , since the context also determines what dynamic data the helper outputs. For example, the `{{meta_title}}` helper outputs different things based on the current context. If the context is `post` then the helper knows it can use `post.meta_title` and in a `tag` context it uses `tag.meta_title`. To detect a context in your theme, use the `{{#is}}` helper. For example, in a partial template that is shared between many contexts, using `{{#is}}` passes it a context and only executes the contained block when it is in that context. List of contexts ---------------- * [index](/docs/themes/contexts/index-context/) * [page](/docs/themes/contexts/page/) * [post](/docs/themes/contexts/post/) * [author](/docs/themes/contexts/author/) * [tag](/docs/themes/contexts/tag/) * [error](/docs/themes/contexts/error/) Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Handlebars Theme Structure - Developer Documentation Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) A Ghost theme contains static HTML templates that make use of helpers to output data from your site, and custom CSS for styling. The recommended file structure for a Ghost theme is: # Structure . ├── /assets | └── /css | ├── screen.css | ├── /fonts | ├── /images | ├── /js ├── default.hbs ├── index.hbs [required] └── post.hbs [required] └── package.json [required] An optional `/partials` directory allows you to use partial templates across your site to share blocks of HTML between multiple templates and reduce code duplication. # Structure . ├── /assets ├── /css ├── screen.css ├── /fonts ├── /images ├── /js ├── /partials ├── list-post.hbs ├── default.hbs ├── index.hbs [required] └── post.hbs [required] └── package.json [required] ### Templates Two template files are required: `index.hbs` and `post.hbs`. All other templates are optional. It’s recommended using a `default.hbs` file as a base layout for your theme. If you have significantly different layouts for different pages or content types, use the [dynamic routing](/docs/themes/routing) configuration layer, or use partials to encapsulate common parts of your theme. Theme templates are hierarchical, so one template can extend another template. This prevents base HTML from being repeated. Here are some commonly used templates and their uses: #### default.hbs `default.hbs` is a base template that contains the boring bits of HTML that exist on every page such as ``, `` or `` as well as the required `{{ghost_head}}` and `{{ghost_foot}}` and any HTML for the header and footer. #### index.hbs This is the standard required template for a list of posts. It is also used if your theme does not have a `tag.hbs`, `author.hbs` or `index.hbs` template. The `index.hbs` template usually extends `default.hbs` and is passed a list of posts using the `{{#foreach}}` helper. #### home.hbs An optional template to provide special content for the home page. This template is only used to render `/`. #### post.hbs The required template for a single post which extends `default.hbs` and uses the `{{#post}}` helper to output the post details. Custom templates for individual posts can be created using `post-:slug.hbs`. #### page.hbs An optional template for static pages. If this is not specified then `post.hbs` will be used. Custom templates for individual pages can be mapped to the page using `page-:slug.hbs`. #### custom-{{template-name}}.hbs Optional custom templates that can be selected in the admin interface on a per-post basis. They can be used for both posts and pages. #### tag.hbs An optional template for tag archive pages. If not specifed the `index.hbs` template is used. Custom templates for individual tags can be created using `tag-:slug`. #### author.hbs An optional template for author archive pages. If not specified the `index.hbs` template is used. Custom templates for individual authors can be created using `author{{slug}}`. #### private.hbs An optional template for the password form page on password protected publications. #### error.hbs An optional theme template for any `404` or `500` errors that are not otherwise handled by error- or class-specific templates. If one is not specified Ghost will use the default. #### error-{{error-class}}xx.hbs An optional theme template for errors belonging to a specific class (e.g. `error-4xx.hbs` for `400`\-level errors). A matching error class template is prioritized over both `error.hbs` and the Ghost default template for rendering the error. #### error-{{error-code}}.hbs An optional theme template for status code-specific errors (e.g. `error-404.hbs`). A matching error code template is prioritized over all other error templates for rendering the error. #### amp.hbs An optional theme template for AMP ([Accelerated Mobile Pages](https://www.ampproject.org/docs/get_started/about-amp.html) ). If your theme doesn’t provide an `amp.hbs` file, Ghost will use its default. #### robots.txt Themes can include a robots.txt which overrides the default robots.txt provided by Ghost. The development version of the default theme [Casper](https://github.com/TryGhost/Casper) can be used to explore how Ghost themes work, or you can customise Casper and make it your own! * * * Helpers ------- Ghost templates are constructed from HTML and handlebars helpers. There are a few requirements: In order for a Ghost theme to work, you must make use of the required helpers: `{{asset}}`, `{{body_class}}`, `{{post_class}}`, `{{ghost_head}}`, `{{ghost_foot}}`. Contexts -------- Each page in a Ghost theme belongs to a [context](/docs/themes/contexts/) which is determined by the URL. The context will decide what template will be used, what data is available and what is output by the `{{body_class}}` helper. Styling ------- When building themes it is important to consider the scope of classes and IDs to avoid clashes between your main styling and your post styling. IDs are automatically generated for headings and used inside a post, so it’s best practice to scope things to a particular part of the page. For example: `#themename-my-id` is preferrable to `#my-id`. Development mode ---------------- It is recommended to use a local install to build a custom theme using development mode – review the [local install guide](/docs/install/local/) to get started with your own local install for development. In production mode, template files are loaded and cached by the server. For any changes in a `hbs` file to be reflected, use the `ghost restart` command. Ghost will automatically check for fatal errors when you upload your theme into Ghost admin. For a full validation report during development, use the [GScan tool](https://gscan.ghost.org/) . Package.json ------------ The `package.json` file is required, and sets some information about your theme, so it’s important to keep it up to date with relevant information. To reference a working example of a `package.json` file, review the [Casper file](https://github.com/TryGhost/Casper/blob/main/package.json/) , and for further information about specific details of `package.json` handling, read the [npm docs](https://docs.npmjs.com/files/package.json) . // package.json { "name": "your-theme-name", "description": "A brief explanation of your theme", "version": "0.5.0", "license": "MIT", "author": { "email": "your@email.here" }, "screenshots": { "desktop": "assets/screenshot-desktop.jpg", "mobile": "assets/screenshot-mobile.jpg" }, "config": { "posts_per_page": 10, "image_sizes": {}, "card_assets": true } } The data in the file must be valid JSON, including double quotes around all property names. Every property except the last one should be separated by a comma. Additional properties --------------------- Here are some of the most common optional properties that can be used in the `package.json` file: * `config.posts_per_page` — the default number of posts per page is 5 * `config.image_sizes` — read more about using [image sizes](/docs/themes/assets/) guide for more details * `config.card_assets` — configure the [card CSS and JS](/docs/themes/content/#editor-cards) that Ghost automatically includes * `config.custom` - add [custom settings](/docs/themes/custom-settings/) to your theme * `description` — provides a short description about your theme and what makes it unique * `docs` - include a URL to docs about how to use the theme. The link to the docs will be available in Ghost Admin on the **Design** page * `license` — use a valid licence string, we recommend `MIT` 😉 Changes to the `package.json` require a restart using the `ghost restart` command. Next steps ---------- The rest of the theme documentation explores how [contexts](/docs/themes/contexts/) and [helpers](/docs/themes/helpers/) work, and serves as a useful reference list for your theme development. For community led support about theme development, visit [the forum](https://forum.ghost.org/c/themes/) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Handlebars Themes - Functional Helpers Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Helpers add additional functionally to Handlebars, the templating language Ghost themes use. Functional helpers ------------------ Functional helpers are used to work with data objects. Use this reference list to discover what each handlebars helper can do when building a custom Ghost theme. | Tag | Description | | --- | --- | | [foreach](/docs/themes/helpers/foreach/) | Loop helper designed for working with lists of posts | | [get](/docs/themes/helpers/get/) | Special block helper for custom queries | | [has](/docs/themes/helpers/has/) | Like `{{#if}}` but with the ability to do more than test a boolean | | [if](/docs/themes/helpers/if/) | Test very simple conditionals | | [is](/docs/themes/helpers/is/) | Check the context of the current route | | [match](/docs/themes/helpers/match/) | Compare two values for equality | | [unless](/docs/themes/helpers/unless/) | The opposite of `{{#if}}` | Data helpers ------------ Data helpers are used to output data from your site. Use this reference list to discover what each handlebars helper can do when building a custom Ghost theme. | Tag | Description | | --- | --- | | [@config](/docs/themes/helpers/config/) | Provides access to global data properties | | [@custom](/docs/themes/helpers/custom/) | Provides access to custom theme settings | | [@page](/docs/themes/helpers/page/) | Provides access to page settings | | [@site](/docs/themes/helpers/site/) | Provides access to global settings | | [@member](/docs/themes/members/#the-member-object) | Provides access to member data | | [authors](/docs/themes/helpers/authors/) | Outputs the post author(s) | | [comments](/docs/themes/helpers/comments/) | Outputs Ghost's member-based commenting system | | [content](/docs/themes/helpers/content/) | Outputs the full post content as HTML | | [date](/docs/themes/helpers/date/) | Outputs the date in a format of your choosing | | [excerpt](/docs/themes/helpers/excerpt/) | Outputs the custom excerpt, or the post content with HTML stripped | | [facebook](/docs/themes/helpers/facebook/) | Outputs the full URL to the Facebook profile from Settings | | [social\_url](/docs/themes/helpers/social_url/) | Outputs the full URL to a social profile | | [img\_url](/docs/themes/helpers/img_url/) | Outputs the correctly calculated URL for the provided image property | | [link](/docs/themes/helpers/link/) | Creates links with dynamic classes | | [navigation](/docs/themes/helpers/navigation/) | Helper which outputs formatted HTML for navigation links | | [post](/docs/themes/helpers/post/) | More `object` than helper – Contains all data for a specific post | | [price](/docs/themes/helpers/price/) | Outputs a price with formatting options | | [readable\_url](/docs/themes/helpers/readable_url/) | Returns a human-readable URL | | [recommendations](/docs/themes/helpers/recommendations/) | Outputs a list of recommended sites | | [tags](/docs/themes/helpers/tags/) | Outputs the post tags | | [tiers](/docs/themes/helpers/tiers/) | Outputs the post tier(s) | | [title](/docs/themes/helpers/title/) | The post title, when inside the `post` scope | | [total\_members](/docs/themes/helpers/total_members/) | Outputs the number of members, rounded and humanised | | [total\_paid\_members](/docs/themes/helpers/total_paid_members/) | Outputs the number of paying members, rounded and humanised | | [twitter](/docs/themes/helpers/twitter/) | Outputs the full URL to the Twitter profile from Settings | | [url](/docs/themes/helpers/url/) | The post URL, when inside the `post` scope | Utility helpers --------------- Utility helpers are used to perform minor, optional tasks. Use this reference list to discover what each handlebars helper can do when building a custom Ghost theme. | Tag | Description | | --- | --- | | [asset](/docs/themes/helpers/asset/) | Outputs cachable and cache-busting relative URLs to various asset types | | [block](/docs/themes/helpers/block/) | Used along with `{{contentFor}}` to pass data up and down the template hierarchy | | [body\_class](/docs/themes/helpers/body_class/) | Outputs dynamic CSS classes intended for the `` tag | | [concat](/docs/themes/helpers/concat/) | Concatenate and link multiple things together | | [encode](/docs/themes/helpers/encode/) | Encode text to be safely used in a URL | | [ghost\_head](/docs/themes/helpers/ghost_head_foot/)
/ [ghost\_foot](/docs/themes/helpers/ghost_head_foot/) | Outputs vital system information at the top and bottom of the document | | [link\_class](/docs/themes/helpers/link_class/) | Add dynamic classes depending on the currently viewed page | | [log](/docs/themes/helpers/log/) | In development mode, output data in the console | | [pagination](/docs/themes/helpers/pagination/) | Helper which outputs formatted HTML for pagination links | | [partials](/docs/themes/helpers/partials/) | Include chunks of reusable template code | | [plural](/docs/themes/helpers/plural/) | Output different text based on a given input | | [post\_class](/docs/themes/helpers/post_class/) | Outputs classes intended for your post container | | [prev\_post](/docs/themes/helpers/prev_next_post/)
/ [next\_post](/docs/themes/helpers/prev_next_post/) | Within the `post` scope, returns the URL to the previous or next post | | [reading\_time](/docs/themes/helpers/reading_time/) | Renders the estimated reading time for a post | | [search](/docs/themes/helpers/search/) | Output a working, pre-styled search button & icon | | [translate](/docs/themes/helpers/translate/) | Output text in your site language (the backbone of i18n) | Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Theme Development: Adding search to a theme Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost has a native search feature that can be accessed via URL or implemented directly into themes using a single data attribute. ![Search](/images/docs/themes/search-in-ghost_huac1f49fc436e5098ff1c22395a576ebf_186857_1074x0_resize_q100_h2_box_3.webp) The easiest way to get started with search is by adding a `#/search` URL to the navigation or anywhere on the site. Beyond that, it’s also possible to implement search directly into a theme using a data attribute. Implementing Search in themes ----------------------------- The quickest way is to use the `{{search}}` helper to output a button with a search icon. See [the helper docs](/docs/themes/helpers/search) for more details. Alternatively, add the `data-ghost-search` data attribute to any element in the theme. Here’s an example from the default theme [Casper](https://github.com/TryGhost/Casper/blob/81d036d4ca036f454f96173a650dd4acc6bb3ca0/default.hbs#L45) : Both methods allow visitors to search content by clicking on the element to open the search modal or by using the shortcut `Cmd/Ctrl + K`. ### Technical details * [Taxonomies](https://ghost.org/docs/themes/routing/#taxonomies) for tags and authors must be present for search results to include tags and authors * The post title and excerpt are used to search post content from the most recent 10,000 posts. (Excerpts are excluded for member-only posts) Create an advanced search index using Algolia --------------------------------------------- If you have a large site with more than 10,000 posts, a complex data structure, or require advanced search functionality, we recommend using Algolia. Ghost has open-source tools to pre-populate the Algolia search index and keep the index updated using webhooks and Netlify Functions. ### Populating the index To make full use of Algolia from the start, you can pre-populate the search index. [Algolia Ghost CLI](https://github.com/TryGhost/algolia/tree/main/packages/algolia) is a tool that creates fragments of content from your Ghost site and adds them to your Algolia search index. Follow the documentation for [Algolia Ghost CLI](https://github.com/TryGhost/algolia/tree/main/packages/algolia) to pre-populate your Algolia search index. ### Setting up Algolia Netlify The best way to keep your Algolia search index updated with new and edited content is to use Netlify Functions, which listen to and processes webhook events and instruct Algolia to index, reindex, or unindex a URL. Once set up, it will automatically keep the search index up to date. You can deploy and configure the [Algolia Netlify](https://github.com/TryGhost/algolia/tree/main/packages/algolia-netlify) package to Netlify in the browser. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Theme Development: Building custom membership flows Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) The Members feature allows you to turn any site into a membership business with member signup, paid subscriptions and email newsletters. Members can be activated using any theme by using the Portal feature — an embeddable memberships feature that can be enabled and customised from the Admin UI. Portal screens can also be accessed in your theme via URLs or data attributes. ![Portal Links](/images/docs/members/portal-links-admin_hu72ea77dfe2902b5b8f1e717e5c1c751c_474136_2376x0_resize_q100_h2_box_3.webp) Portal links can use absolute or relative links, for example: // Absolute URLs takes readers to the homepage and opens a Portal screen. Subscribe // Relative URLs open Portal on the current page. Subscribe When using the `data-portal` data attribute to control the Portal UI, additional classes `gh-portal-open` and `gh-portal-close` are added to the element to allow custom styling of open and closed states. Alternatively, it’s possible to build entirely custom membership and signup flows directly into a theme using this guide. * * * Signup forms ------------ Add the `data-members-form` attribute to the form element and `data-members-email` to the email input field to create a standard email collection signup form:
Add `data-members-name` to an input element to capture a member’s name at signup:
Capture form errors with `data-members-error`. Errors could include too many attempts to sign up or trying to subscribe to a newsletter that no longer exists (see below):

### Newsletter subscriptions When a member signs up via the form, they are subscribed to the site’s default newsletter. However, it’s also possible to specify which newsletters members are subcribed to on signup by adding `data-members-newsletter` to an input element. In the example below, the member is subscribed to the Weekly Threads newsletter.
...
Subscribe a member to multiple newsletters by including additional `input` elements:
...
By using `hidden` inputs in the examples above, newsletter details are hidden from the user. But, you can allow users to choose which newsletters to subscribe to by using `radio` or `checkbox` elements:
...
Create a dynamic signup form at the theme level by using the [`get` helper](/docs/themes/helpers/get/) to fetch a site’s `newsletters`. Then, loop through the newsletters and add the `name` property to an `input` element. See below for an example implementation:
{{#get "newsletters"}} {{#foreach newsletters}} {{/foreach}} {{/get}}
### Extending forms The `data-members-form` accepts a series of optional values to customise user flows: * `data-members-form="signin"` – sends a signin email to existing members when a valid email is entered. * `data-members-form="signup"` – sends a signup email to new members. Uses “sign up” in email text. If a valid email is present, a signin email is sent instead. * `data-members-form="subscribe"` – sends a subscribe email. Uses “subscription” in email text. If a valid email is present, a signin email is sent instead. * `data-members-autoredirect="false"` - when set to `false`, the user will be redirected to the publication’s homepage when logging in. When set to `true` (the default), the user will be redirected to the page where they signed up. ### Form states Member forms pass a series of states, which are reflected in the HTML as classes for when the submission is loading, when the submission was successful, or when there is an error.
...
...
...
### Signing out Give members the option to sign out of your site by creating a sign out button or link using the `data-members-signout` data attribute. Sign out Using the `@member` object in conjunction with a sign out button allows you to show the signin link when the member is logged out, and a sign out link if a member is logged in. {{#if @member}} Sign out {{else}}
{{/if}} ### Apply labels from a form Labels are useful for managing, segmenting and auditing a members list, and can be applied manually in Ghost Admin, or automatically via a form, an integration or the API. Apply labels from a specific a signup form using a hidden HTML input element, for example:
### Error messages Implement error messages when a form or subscription button causes an error by adding a child element to the `
` or `` element with the attribute `data-members-error`. ...

* * * Content visibility ------------------ Control how members access content on your site, and what content they’re able to read in full as a logged in member. ### Content All members that are logged in to your site have an access level attached to them. To correspond, all posts have a `visibility` setting attached to the `content`. This setting is applied in the Admin UI as the [post access level](/help/protected-content/) on each individual post. ### Access `access` is a variable that calculates the access level of the member viewing the post and the access level setting applied to the post. `access` will return `true` if the member’s access matches, or exceeds, the access level of the post, and `false` if it doesn’t match. {{#post}}

{{title}}

{{#if access}}

Thanks for being a member...

{{else}}

You need to become a member in order to read this post...

{{/if}} {{content}} {{/post}} ### Default CTA With the `{{content}}` helper, visitors who don’t have access to a post (determined by the `access` property) will see a default call to action in the content area instead, prompting users to upgrade their subscription. Used in conjunction with a free public preview in post content, the CTA will be displayed after the free preview. ![Content CTA](/images/docs/members/content-cta_hu3d640371aa932b7b360881a3df965f9b_54918_1462x0_resize_q100_h2_box_3.webp) The default CTA can be overridden by providing a `./partials/content-cta.hbs` template file in your theme. The default content CTA [provided by Ghost](https://github.com/TryGhost/Ghost/blob/3d989eba2371235d41468f7699a08e46fc2b1e87/ghost/core/core/frontend/helpers/tpl/content-cta.hbs) may be used as a reference. ### The `@member` object The `@member` object can be used to determine which content within the theme is exposed depending on the access level of the member. This is achieved using an `#if` statement: {{#if @member}}

Thanks for becoming a member 🎉

{{else}}

You should totally sign up... 🖋

{{/if}} Using `@member.paid` allows you to expose different content to members who have an active paid subscription. {{#if @member.paid}}

Thanks for becoming a paying member 🎉

{{/if}} `@member.paid` returns `true` for members with active subscriptions in states “active”, “trialing”, “unpaid” and “past\_due”. To revoke access for members with failing payments, update your [Stripe settings](https://dashboard.stripe.com/settings/billing/automatic) to automatically cancel subscriptions after all payment attempts have failed. These two boolean values can be used together to customise UI and messages within a theme to a particular segment of your audience: {{#if @member.paid}}

Thanks for becoming a paying member 🎉

{{else if @member}}

Thanks for being a member 🙌

{{else}}

You should totally sign up... 🖋

{{/if}} ### Visibility The `visibility` attribute is relative to the post or page, and is useful for providing templates with extra attribute information depending on the viewer status. `visibility` has 3 possible values: `public`, `members` or `paid` .

{{title}}

{{content}}
An example use case could be to show a particular icon next to the title of a post:

{{title}}

### `visibility` in posts By default, all posts (including those that are set to `members-only` or `paid-members only`) will appear in post archives unless the `visibility` parameter is included with the `#foreach` helper: {{#foreach visibility="paid"}}
{{/foreach}} The content of the posts is still restricted based on the access level of the logged in member. ### `visibility` with `#has` Using the visibility flag with the `#has` helper allows for more unique styling between `public`, `members` and `paid` posts. For example: {{#foreach posts}}
{{#has visibility="paid"}} Premium {{/has}}

{{title}}

{{/foreach}} * * * Checkout buttons ---------------- Turn your membership site into a business with paid subscriptions via Stripe, to offer paid content on a monthly or yearly basis. ### Subscription plans There are currently two types of plans for each tier in Members: monthly and yearly. [Find out how to connect a Stripe account.](/help/setup-members/#connect-a-stripe-account/) . Once Stripe is properly configured, it’s possible to direct visitors to a Stripe payment form pre-filled with the selected plan, by adding buttons with the `data-portal` attribute pointing to monthly or yearly price of a tier. The data attribute for monthly/yearly plan of a tier can be fetched from Portal settings in your Admin URL - `/ghost/#/settings/members?showPortalSettings=true`. Monthly plan Yearly plan * * * Member profile pages -------------------- It’s possible to expose information about a member in a Ghost theme to allow members to manage their own subscriptions, or update their details when logged in. ![Example theme member account](/images/docs/members/theme-account-example_hua1cc91f659d30ed537e78ceeee649a6e_60374_800x0_resize_q100_h2_box_3.webp) ### Member attributes The `@member` object has a series of attributes that expose the information required to create a member profile page: * `@member` – The member object, evaluates to `true` or `false` if the viewer is a member or not * `@member.paid` – The member’s payment status, returns `true` or `false` if the member has an active paid subscription * `@member.email` – The member’s email address * `@member.name` – The member’s full name * `@member.firstname` – The member’s first name (everything before the first whitespace character in the member’s full name) * `@member.uuid` – A unique identifier for a member for use with analytics tracking such as Google Tag Manager ### Member subscriptions It’s also possible to retrieve and expose information about a member’s subscription using data that comes from Stripe using `@member.subscriptions`. Members may have multiple subscriptions, provided as an array. Subscription data can be exposed using a `#foreach`: {{#foreach @member.subscriptions}}

Name: {{customer.name}}

Plan type: {{plan.nickname}}

Status: {{status}}

{{/foreach}} ### Subscription attributes Subscription data comes from Stripe meaning a valid Stripe account connected to Ghost is required. Using subscription data in a local environment requires the [Stripe CLI tool](https://stripe.com/docs/stripe-cli) . * `id` – The Stripe ID of the subscription * `avatar_image` — The customers avatar image, pulled in from [Gravatar](https://en.gravatar.com/) . If there is not one set for their email a transparent `png` will be returned as a default * `customer.id` – The Stripe ID of the customer * `customer.name` – The name of the customer in Stripe * `customer.email` – The email of the customer in Stripe * `plan.id` – The Stripe ID of the plan * `plan.nickname` – The Stripe nickname of the plan (currently only “Monthly” or “Yearly”) * `plan.interval` – The Stripe plan payment interval (currently only “month” or “year”) * `plan.currency` – The currency code of the plan as an ISO currency code * `plan.amount` – The amount of the Stripe plan in the smallest currency denomination (e.g. USD $5 would be “500” cents) * `status` – The status of the subscription (can be one of: “active”, “trialing”, “unpaid”, “past\_due”, “canceled”) * `start_date` – The date which the subscription was first started, can be used with the `{{date}}` helper * `default_payment_card_last4` – The last 4 digits of the card that paid the subscription * `current_period_end` – The date which the subscription has been paid up until, can be used with the `{{date}}` helper ### Member account editing Members may want to update their billing information. Rather than contacting the site owner the member can be linked to a page to update their details with a single button: Edit billing info Additional attributes can be used to direct the member to different URLs if they update their billing information or cancel their subscription: Edit billing info ### The `price` helper The `{{price}}` helper formats monetary values from their smallest denomination to a human readable denomination with currency formatting. This is best used in the context of a subscription plan to format Stripe plan amounts (see `plan.amount` above) or outputting prices for tiers. Example: {{price plan}} This will output `$5`. The `{{price}}` helper has many options with detailed documentation [here](/docs/themes/helpers/price/) . ### Cancel links The `{{cancel_link}}` helper is designed to output links to cancel or continue a subscription, so that your members can manage their own subscriptions. This helper wraps all of the internals needed to cancel an active subscription or to continue the subscription if it was previously canceled. The helper must be used in the `@member.subscriptions` context, for example: {{#foreach @member.subscriptions}} {{cancel_link}} {{/foreach}} The HTML markup generated by this code looks like this: Cancel subscription The `{{cancel_link}}` helper accepts a number of optional attributes: * `class` - defaults to `gh-subscription-cancel` * `errorClass` - defaults to `gh-error gh-error-subscription-cancel` * `cancelLabel` - defaults to `Cancel subscription` * `continueLabel` - defaults to `Continue subscription` Here’s an example of how you can use the helper with all of the attributes: {{cancel_link class="cancel-link" errorClass="cancel-error" cancelLabel="Cancel!" continueLabel="Continue!" }} This would produce the following HTML for previously canceled subscription: Continue! Here’s an example of the `{{cancel_link}}` helper in use in the members-enabled theme [Lyra](https://github.com/TryGhost/Lyra/) within the [account.hbs](https://github.com/TryGhost/Lyra/blob/4ca9576/members/account.hbs/#L15-L65) file. It’s used inside a `{{#foreach @member.subscriptions}}` loop which provides the helper the context needed to generate an appropriate link, and is surrounded by other useful information displayed to the member. {{#foreach @member.subscriptions}}
{{#if cancel_at_period_end}}

Your subscription will expire on {{date current_period_end format="DD MMM YYYY"}}. If you change your mind in the mean time you can turn auto-renew back on to continue your subscription.

{{else}}

Hey! You have an active {{@site.title}} account with access to all areas. Get in touch if have any problems or need some help getting things updated, and thanks for subscribing.

{{/if}}
{{@member.email}}
{{price plan}}/{{plan.interval}}
**** **** **** {{default_payment_card_last4}}
{{date current_period_end format="DD MMM YYYY"}}
{{cancel_link}}
{{/foreach}} Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Configuration - Adapt your publication to suit your needs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) For self-hosted Ghost users, a custom configuration file can be used to override Ghost’s default behaviour. This provides you with a range of options to configure your publication to suit your needs. Overview -------- When you install Ghost using the supported and recommended method using `ghost-cli`, a custom configuration file is created for you by default. There are some configuration options which are required by default, and many optional configurations. The three required options are `url` and `database` which are configured during setup, and `mail` which needs to be configured once you’ve installed Ghost. This article explains how to setup your mail config, as well as walk you through all of the available config options. Custom configuration files -------------------------- The configuration is managed by [nconf](https://github.com/indexzero/nconf/) . A custom configuration file must be a valid JSON file located in the root folder and changes to the file can be implemented using `ghost restart`. Since Node.js has the concept of environments built in, Ghost supports two environments: **development** and **production**. All public Ghost publications run in production mode, while development mode can be used to test or build on top of Ghost locally. > Check out the official install guides for [development](/docs/install/local/) > and [production](/docs/install/ubuntu/) > . The configuration files reflect the environment you are using: * `config.development.json` * `config.production.json` #### Ghost in development If you would like to start Ghost in development, you don’t have to specify any environment, because development is default. To test Ghost in production, you can use: NODE_ENV=production node index.js If you want to make changes when developing and working on Ghost, you can create a special configuration file that will be ignored in git: * `config.local.json` This file is merged on top of `config.development.json` so you can use both at the same time. #### Debugging the configuration output Start Ghost with: DEBUG=ghost:*,ghost-config node index.js #### Running Ghost with config env variables Start Ghost using environment variables which match the name and case of each config option: url=http://ghost.local:2368 node index.js For nested config options, separate with two underscores: database__connection__host=mysql node index.js Configuration options --------------------- There are a number of configuration options which are explained in detail in this article. Below is an index of all configuration options: | Name | Required? | Description | | --- | --- | --- | | `url` | In production | Set the public URL for your blog | | `database` | In production | Type of database used (default: MySQL) | | `mail` | In production | Add a mail service | | `admin` | Optional | Set the protocol and hostname for your admin panel | | `server` | Optional | Host and port for Ghost to listen on | | `privacy` | Optional | Disable features set in [privacy.md](https://github.com/TryGhost/Ghost/blob/2f09dd888024f143d28a0d81bede1b53a6db9557/PRIVACY.md) | | `security` | Optional | Disable security features that are enabled by default | | `paths` | Optional | Customise internal paths | | `referrerPolicy` | Optional | Control the content attribute of the meta referrer tag | | `useMinFiles` | Optional | Generate assets URL with .min notation | | `storage` | Optional | Set a custom storage adapter | | `scheduling` | Optional | Set a custom scheduling adapter | | `logging` | Optional | Configure logging for Ghost | | `spam` | Optional | Configure spam settings | | `caching` | Optional | Configure HTTP caching settings | | `compress` | Optional | Disable compression of server responses | | `imageOptimization` | Optional | Configure image manipulation and processing | | `opensea` | Optional | Increase rate limit for fetching NFT embeds from OpenSea.io | | `tenor` | Optional | Enable integration with Tenor.com for embedding GIFs directly from the editor | | `twitter` | Optional | Add support for rich Twitter embeds in newsletters | | `portal` | Optional | Relocate or remove the scripts for Portal | | `sodoSearch` | Optional | Relocate or remove the scripts for Sodo search | | `comments` | Optional | Relocate or remove the scripts for comments | ### URL _(Required in production)_ Once a Ghost publication is installed, the first thing to do is set a URL. When installing using `ghost-cli`, the install process requests the URL during the setup process. Enter the URL that is used to access your publication. If using a subpath, enter the full path, `https://example.com/blog/`. If using SSL, always enter the URL with `https://`. #### SSL We always recommend using SSL to run your Ghost publication in production. Ghost has a number of configuration options for working with SSL, and securing the URLs for the admin `/ghost/` and the frontend of your publication. Without SSL your username and password are sent in plaintext. `ghost-cli` prompts to set up SSL during the installation process. After a successful SSL setup, you can find your SSL certificate in `/etc/letsencrypt`. If you see errors such as `access denied from url`, then the provided URL in your config file is incorrect and needs to be updated. ### Database _(Required in production)_ Ghost is configured using MySQL by default: "database": { "client": "mysql", "connection": { "host": "127.0.0.1", "port": 3306, "user": "your_database_user", "password": "your_database_password", "database": "your_database_name" } } Alternatively, you can configure sqlite3: "database": { "client": "sqlite3", "connection": { "filename": "content/data/ghost-test.db" }, "useNullAsDefault": true, "debug": false } #### Number of connections It’s possible to limit the number of simultaneous connections using the pool setting. The default values are a minimum of 2 and a maximum of 10, which means Ghost always maintains two active database connections. You can set the minimum to 0 to prevent this: "database": { "client": ..., "connection": { ... }, "pool": { "min": 2, "max": 20 } } #### SSL In a typical Ghost installation, the MySQL database will be on the same server as Ghost itself. With cloud computing and database-as-a-service providers you might want to enable SSL connections to the database. For Amazon RDS you’ll need to configure the connection with `"ssl": "Amazon RDS"`: "database": { "client": "mysql", "connection": { "host": "your_cloud_database", "port": 3306, "user": "your_database_user", "password": "your_database_password", "database": "your_database_name", "ssl": "Amazon RDS" } } For other hosts, you’ll need to output your CA certificate (not your CA private key) as a single line string including literal new line characters `\n` (you can get the single line string with `awk '{printf "%s\\n", $0}' CustomRootCA.crt`) and add it to the configuration: "database": { "client": "mysql", "connection": { "host": "your_cloud_database", "port": 3306, "user": "your_database_user", "password": "your_database_password", "database": "your_database_name", "ssl": { "ca": "-----BEGIN CERTIFICATE-----\nMIIFY... truncated ...pq8fa/a\n-----END CERTIFICATE-----\n" } } } For a certificate chain, include all CA certificates in the single line string: "database": { "client": "mysql", "connection": { "host": "your_cloud_database", "port": 3306, "user": "your_database_user", "password": "your_database_password", "database": "your_database_name", "ssl": { "ca": "-----BEGIN CERTIFICATE-----\nMIIFY... truncated ...pq8fa/a\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFY... truncated ...wn8v90/a\n-----END CERTIFICATE-----\n" } } } ### Mail _(Required in production)_ The most important piece of configuration once the installation is complete is to set up mail. Configuring mail allows Ghost to send transactional emails such as user invitations, password resets, member signups, and member login links. With the help of a bulk email service, you can also configure Ghost to send newsletters to members. Ghost uses [Nodemailer](https://github.com/nodemailer/nodemailer/) under the hood, and tries to use the direct mail service if available. We recommend ensuring transactional emails are functional before moving on to bulk mail configuration. #### Configuring with Mailgun [Mailgun](https://www.mailgun.com/) is a service for sending emails and provides more than adequate resources to send bulk emails at a reasonable price. Find out more about [using Mailgun with Ghost here](/docs/faq/mailgun-newsletters/) . Mailgun allows you to use your own domain for sending transactional emails. Otherwise, you can use a subdomain that Mailgun provides you with (also known as the sandbox domain, limited to 300 emails per day). You can change this at any time. Mailgun is an optional service for sending transactional emails, but it is required for bulk mail — [read more](/docs/faq/mailgun-newsletters/) . #### Create a Mailgun account Once your site is fully set up [create a Mailgun account](https://signup.mailgun.com/) . After your account is verified navigate to **Domain settings** under **Sending** in the Mailgun admin. There you’ll find your SMTP credentials. ![Mailgun SMTP settings view](/images/docs/concepts/mailgun-smtp_hub94c62b257175129863d85e1a9325a52_48235_866x0_resize_q100_h2_box_3.webp) In addition to this information, you’ll need the password, which can be obtained by clicking the **Reset Password** button. Keep these details for future reference. Mailgun provides options for using their own subdomains for sending emails, as well as custom domains for a [competitive price](/docs/faq/mailgun-newsletters/#did-you-know-mailgun-doesn-t-have-free-accounts-anymore) . #### Add credentials to `config.production.json` Open your production config file in any code editor and add the following mail configuration, making sure to update the values to the same credentials shown in your own Mailgun SMTP settings: // config.production.json "mail": { "transport": "SMTP", "options": { "service": "Mailgun", "auth": { "user": "postmaster@example.mailgun.org", "pass": "1234567890" } } }, Once you are finished, hit save and then run `ghost restart` for your changes to take effect. These same credentials can be used for development environments, by adding them to the `config.development.json` file. Mailgun provides a sandbox mode, which restricts emails to authorized recipients. Once sandbox mode is enabled, add and verify the email addresses you want to send emails to prior to testing. #### Secure connection Depending on your Mailgun settings you may want to force a secure SMTP connection. Update your `config.production.json` with the following for a secure connection: // config.production.json "mail": { "transport": "SMTP", "options": { "service": "Mailgun", "host": "smtp.mailgun.org", "port": 465, "secure": true, "auth": { "user": "postmaster@example.mailgun.org", "pass": "1234567890" } } }, As always, hit save and run `ghost restart` for your changes to take effect. #### Amazon SES It’s also possible to use [Amazon Simple Email Service](https://aws.amazon.com/ses/) . Use the SMTP username and password given when signing up and configure your `config.[env].json` file as follows: "mail": { "transport": "SMTP", "options": { "host": "YOUR-SES-SERVER-NAME", "port": 465, "service": "SES", "auth": { "user": "YOUR-SES-SMTP-ACCESS-KEY-ID", "pass": "YOUR-SES-SMTP-SECRET-ACCESS-KEY" } } } #### Email addresses **Default email address** Ghost uses the value set in `mail.from` as the default email address: "mail": { "from": "support@example.com", } A custom name can also optionally be provided: "mail": { "from": "'Acme Support' ", } Try to use a real, working email address - as this greatly improves delivery rates for important emails sent by Ghost (Like password reset requests and user invitations). If you have a company support email address, this is a good place to use it. **Support email address** When setting a custom support email address via **Settings** → **Portal settings** → **Account page**, you override the default email address for member communications like sign-in/sign-up emails and member notifications. **Newsletter addresses** It’s also possible to set a separate sender and reply-to address per newsletter, which will be used instead of the default. Configure these addresses via **Settings** → **Newsletters**. The table below shows which email is used based on email type. In the table, if an address is not, it falls back to the next available address until reaching the default. | Email type | Address used | Examples | | --- | --- | --- | | Member notifications | Support, Default | Signup/sign links, comment replies | | Newsletters | Newsletter, Default | Configurable per newsletter | | Staff notifications | Default | Recommendations, signups | Irrespective of how you configure email addresses, maximize deliverability by ensuring DKIM, SPF, and DMARC records are configured for your sending domains. ### Admin URL Admin can be used to specify a different protocol for your admin panel or a different hostname (domain name). It can’t affect the path at which the admin panel is served (this is always /ghost/). "admin": { "url": "http://example.com" } ### Server The server host and port are the IP address and port number that Ghost listens on for requests. By default, requests are routed from port 80 to Ghost by nginx (recommended), or Apache. "server": { "host": "127.0.0.1", "port": 2368 } ### Privacy All features inside the privacy.md file are enabled by default. It is possible to turn these off in order to protect privacy: * Update check * Gravatar * RPC ping * Structured data For more information about the features, read the [privacy.md page](https://github.com/TryGhost/Ghost/blob/2f09dd888024f143d28a0d81bede1b53a6db9557/PRIVACY.md) . To turn off **all** of the features, use: "privacy": { "useTinfoil": true } Alternatively, configure each feature individually: "privacy": { "useUpdateCheck": false, "useGravatar": false, "useRpcPing": false, "useStructuredData": false } ### Security By default Ghost will email an auth code when it detects a login from a new device. To disable this feature, use: "security": { "staffDeviceVerification": false } Note: if you want to force 2FA for all staff logins, not just new devices, you can do so under the Settings > Staff in the admin panel ### Paths The configuration of paths can be relative or absolute. To use a content directory that does not live inside the Ghost folder, specify a paths object with a new contentPath: "paths": { "contentPath": "content/" }, When using a custom content path, the content directory must exist and contain subdirectories for data, images, themes, logs, and adapters. > If using a SQLite database, you’ll also need to update the path to your database to match the new location of the data folder. ### Referrer Policy Set the value of the content attribute of the meta referrer HTML tag by adding referrerPolicy to your config. `origin-when-crossorigin` is the default. Read through all possible [options](https://www.w3.org/TR/referrer-policy/#referrer-policies/) . Adapters -------- Ghost allows for customizations at multiple layers through an adapter system. Customizable layers include: `storage`, `caching`, `sso`, and `scheduling`. Use the `adapters` configuration block with “storage”, “caching”, “sso,” or “scheduling” keys to initialize a custom adapter. For example, the following configuration uses `storage-module-name` to handle all `storage` capabilities in Ghost. Note that the `active` key indicates a default adapter used for all features if no other adapters are declared. "adapters": { "storage": { "active": "storage-module-name", "storage-module-name": { "key": "value" } } } Customize parts of Ghost’s features by declaring adapters at the feature level. For example, to use a custom `cache` adapter only for the `imageSizes` feature, configure the cache adapter as follows: "adapters": { "cache": { "custom-redis-cache-adapter": { "host": "localhost", "port": 6379, "password": "secret_password" }, "imageSizes": { "adapter": "custom-redis-cache-adapter", "ttl": 3600 } } } The above declaration uses the `custom-redis-cache-adapter` only for the `imageSizes` cache feature with these values: { "host": "localhost", "port": 6379, "password": "secret_password", "ttl": 3600 } ### Storage adapters The storage layer is used to store images uploaded from the Ghost Admin UI, API, or when images are included in a zip file uploaded via the importer. Using a custom storage module allows you to change where images are stored without changing Ghost core. By default, Ghost stores uploaded images in the file system. The default location is the Ghost content path in your Ghost folder under `content/images` or an alternative custom content path that’s been configured. To use a custom storage adapter, your custom configuration file needs to be updated to provide configuration for your new storage module and set it as active: "storage": { "active": "my-module", "my-module": { "key": "abcdef" } } The storage block should have 2 items: * An active key, which contains the name\* of your module * A key that reflects the name\* of your module, containing any config your module needs #### Available storage features * `images` - storage of image files uploaded through `POST '/images/upload'` endpoint * `media` - storage of media files uploaded through `POST '/media/upload'` and `POST/media/thumbnail/upload` endpoints * `files` - storage of generic files uploaded through `POST '/files/upload'` endpoint #### Available custom storage adapters * [local-file-store](https://github.com/TryGhost/Ghost/blob/fa1861aad3ba4e5e1797cec346f775c5931ca856/ghost/core/core/server/adapters/storage/LocalFilesStorage.js) (default) saves images to the local filesystem * [http-store](https://gist.github.com/ErisDS/559e11bf3e84b89a9594) passes image requests through to an HTTP endpoint * [s3-store](https://github.com/spanishdict/ghost-s3-compat) saves to Amazon S3 and proxies requests to S3 * [s3-store](https://github.com/colinmeinke/ghost-storage-adapter-s3) saves to Amazon S3 and works with 0.10+ * [qn-store](https://github.com/Minwe/qn-store) saves to Qiniu * [ghost-cloudinary-store](https://github.com/mmornati/ghost-cloudinary-store) saves to Cloudinary * [ghost-storage-cloudinary](https://github.com/eexit/ghost-storage-cloudinary) saves to Cloudinary with RetinaJS support * [upyun-ghost-store](https://github.com/sanddudu/upyun-ghost-store) saves to Upyun * [ghost-upyun-store](https://github.com/pupboss/ghost-upyun-store) saves to Upyun * [ghost-google-drive](https://github.com/robincsamuel/ghost-google-drive) saves to Google Drive * [ghost-azure-storage](https://github.com/tparnell8/ghost-azurestorage) saves to Azure Storage * [ghost-imgur](https://github.com/wrenth04/ghost-imgur) saves to Imgur * [google-cloud-storage](https://github.com/thombuchi/ghost-google-cloud-storage) saves to Google Cloud Storage * [ghost-oss-store](https://github.com/MT-Libraries/ghost-oss-store) saves to Aliyun OSS * [ghost-b2](https://github.com/martiendt/ghost-storage-adapter-b2) saves to Backblaze B2 * [ghost-github](https://github.com/ifvictr/ghost-github) saves to GitHub * [pages-store](https://github.com/zce/pages-store) saves to GitHub Pages or other pages service, e.g. Coding Pages * [WebDAV Storage](https://github.com/bartt/ghost-webdav-storage-adapter) saves to a WebDAV server. * [ghost-qcloud-cos](https://github.com/ZhelinCheng/ghost-qcloud-cos) saves to Tencent Cloud COS. * [ghost-bunny-cdn-storage](https://github.com/betschki/ghost-bunny-cdn-storage/) saves to BunnyCDN. #### Creating a custom storage adapter To replace the storage module with a custom solution, use the requirements detailed below. You can also take a look at our [default local storage implementation](https://github.com/TryGhost/Ghost/blob/fa1861aad3ba4e5e1797cec346f775c5931ca856/ghost/core/core/server/adapters/storage/LocalFilesStorage.js) . ##### Location 1. Create a new folder named `storage` inside `content/adapters` 2. Inside of `content/adapters/storage`, create a file or a folder: `content/adapters/storage/my-module.js` or `content/adapters/storage/my-module` — if using a folder, create a file called `index.js` inside it. ##### Base adapter class inheritance A custom storage adapter must inherit from the base storage adapter. By default, the base storage adapter is installed by Ghost and available in your custom adapter. const BaseAdapter = require('ghost-storage-base'); class MyCustomAdapter extends BaseAdapter{ constructor() { super(); } } module.exports = MyCustomAdapter; ##### Required methods Your custom storage adapter must implement five required functions: * `save` - The `.save()` method stores the image and returns a promise which resolves the path from which the image should be requested in future. * `exists` - Used by the base storage adapter to check whether a file exists or not * `serve` - Ghost calls `.serve()` as part of its middleware stack, and mounts the returned function as the middleware for serving images * `delete` * `read` const BaseAdapter = require('ghost-storage-base'); class MyCustomAdapter extends BaseAdapter{ constructor() { super(); } exists() { } save() { } serve() { return function customServe(req, res, next) { next(); } } delete() { } read() { } } module.exports = MyCustomAdapter; ### Cache adapters The cache layer is used for storing data that needs to be quickly accessible in a format requiring no additional processing. For example, the “imageSizes” cache stores images generated at different sizes based on the fetched URL. This request is a relatively expensive operation, which would otherwise slow down the response time of the Ghost server. Having calculated image sizes cached per image URL makes the image size lookup almost instant with only a little overhead on the initial image fetch. By default, Ghost keeps caches in memory. The upsides of this approach are: * no need for external dependencies * very fast access to data The downsides are: * Having no persistence between Ghost restarts — cache has to be repopulated on every restart * RAM is a limited resource that can be depleted by too many cached values With custom cache adapters, like Redis storage, the cache can expand its size independently of the server’s system memory and persist its values between Ghost restarts. #### Ghost’s built-in Redis cache adapter Ghost’s built-in Redis cache adapter solves the downsides named above by persisting across Ghost restarts and not being limited by the Ghost instance’s RAM capacity. [Implementing a Redis cache](https://redis.io/docs/getting-started/installation/,) is a good solution for sites with high load and complicated templates, ones using lots of `get` helpers. Note that this adapter requires Redis to be set up and running in addition to Ghost. To use the Redis cache adapter, change the value for the cache adapter from “Memory” to “Redis” in the site’s configuration file. In the following example, image sizes and the tags Content API endpoint are cached in Redis for optimized performance. "adapters": { "cache": { "imageSizes": { "adapter": "Redis", "ttl": 3600, "keyPrefix": "image-sizes:" } } }, Note that the `ttl` value is in seconds. #### Custom cache adapters To use a custom cache adapter, update your custom configuration file. At the moment, only the `imageSizes` feature supports full customization. Configuration is as follows: "cache": { "imageSizes": "my-cache-module", "my-cache-module": { "key": "cache_module_value" } } The `cache` block should have 2 items: * A feature key, `"imageSizes"`, which contains the name of your custom caching module * A `key` that reflects the name of your caching module, containing any config your module needs #### Creating a custom cache adapter To replace the caching module, use the requirements below. You can also take a look at our [default in-memory caching implementation](https://github.com/TryGhost/Ghost/blob/eb6534bd7fd905b9f402c1f446c87bff455b6f17/ghost/core/core/server/adapters/cache/Memory.js) . #### Location 1. Create a new folder named `cache` inside `content/adapters` 2. Inside of `content/adapters/cache`, create a file or a folder: `content/adapters/cache/my-cache-module.js` or `content/adapters/cache/my-cache-module` - if using a folder, create a file called `index.js` inside it. #### Base cache adapter class inheritance A custom cache adapter must inherit from the base cache adapter. By default the base cache adapter is installed by Ghost and available in your custom adapter. const BaseCacheAdapter = require('@tryghost/adapter-base-cache'); class MyCustomCacheAdapter extends BaseCacheAdapter{ constructor() { super(); } } module.exports = MyCustomCacheAdapter; #### Required methods Your custom cache adapter must implement the following required functions: * `get` - fetches the stored value based on the key value (`.get('some_key')`). It’s an async method - the implementation returns a `Promise` that resolves with the stored value. * `set` - sets the value in the underlying cache based on key and value parameters. It’s an async method - the implementation returns a `Promise` that resolves once the value is stored. * `keys` - fetches all keys present in the cache. It’s an async method — the implementation returns a `Promise` that resolves with an array of strings. * `reset` - clears the cache. This method is not meant to be used in production code - it’s here for test suite purposes _only_. const BaseCacheAdapter = require('@tryghost/adapter-base-cache'); class MyCustomCacheAdapter extends BaseCacheAdapter { constructor(config) { super(); } /** * @param {String} key */ async get(key) { } /** * @param {String} key * @param {*} value */ async set(key, value) { } /** * @returns {Promise>} all keys present in the cache */ async keys() { } /** * @returns {Promise<*>} clears the cache. Not meant for production */ async reset() { } } module.exports = MyCustomCacheAdapter; #### Redis cache adapter ### Logging Configure how Ghost should log, for example: "logging": { "path": "something/", "useLocalTime": true, "level": "info", "rotation": { "enabled": true, "count": 15, "period": "1d" }, "transports": ["stdout", "file"] } #### `level` The default log level is `info` which prints all info, warning and error logs. Set it to `error` to only print errors. #### `rotation` Tell Ghost to rotate your log files. By default Ghost keeps 10 log files and rotates every day. Rotation is enabled by default in production and disabled in development. #### `transports` Define where Ghost should log to. By default Ghost writes to stdout and into file for production, and to stdout only for development. #### `path` Log your content path, e.g. `content/logs/`. Set any path but ensure the permissions are correct to write into this folder. #### `useLocalTime` Configure log timestamps to use the local timezone. Defaults to `false`. ### Spam Tell Ghost how to treat [spam requests](https://github.com/TryGhost/Ghost/blob/ff61b330491b594997b5b156215417b5d7687743/ghost/core/core/shared/config/defaults.json#L64) . ### Caching Configure [HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching) for HTTP responses served from Ghost. `caching` configuration is available for responses containing `public` value in `Cache-Control` header. Each key under `caching` section contains `maxAge` property that controls the `max-age` value in `Cache-Control` header. For example, the following configuration: "caching": { "contentAPI": { "maxAge": 10 } } Adds `Cache-Control: public, max-age=10` header with all Content API responses, which might be useful to set for high-volume sites where content does not change often. The following configuration keys are available with default `maxAge` values: * “frontend” - with `"maxAge": 0`, controls responses coming from public Ghost pages (like the homepage) * “contentAPI” - with `"maxAge": 0`, controls responses coming from [Content API](https://ghost.org/docs/content-api/) * “robotstxt” - with `"maxAge": 3600`, controls responses for `robots.txt` [files](https://ghost.org/docs/themes/structure/#robotstxt) * “sitemap” - with `"maxAge": 3600`, controls responses for `sitemap.xml` [files](https://ghost.org/changelog/xml-sitemaps/) * “sitemapXSL” - with `"maxAge": 86400`, controls responses for `sitemap.xsl` files * “wellKnown” - with `"maxAge": 86400`, controls responses coming from `*/.wellknown/*` endpoints * “cors” - with `"maxAge": 86400`, controls responses for `OPTIONS` [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) requests * “publicAssets” - with `"maxAge": 31536000`, controls responses for public assets like `public/ghost.css`, `public/cards.min.js`, etc. * “301” - with `"maxAge": 31536000`, controls 301 redirect responses * “customRedirects” - with `"maxAge": 31536000`, controls redirects coming from [custom redirects](https://ghost.org/docs/themes/routing/#redirects) ### Compress The compression flag is turned on by default using `"compress": true`. Alternatively, you can turn it off with `"compress": false`. ### Image optimization When uploading images into the Ghost editor, they are automatically processed and compressed by default. This can be disabled in your `config.[env].json` file using: "imageOptimization": { "resize": false } Image compression details: * Resize the image to 2000px max width * JPEGs are compressed to 80% quality. * Metadata is removed The original image is kept with the suffix `_o`. ### OpenSea When creating NFT embeds, Ghost fetches the information from the [OpenSea](https://opensea.io) API. This API is rate limited, and OpenSea request that you use an API key in production environments. You can [request an OpenSea API key](https://docs.opensea.io/reference/request-an-api-key) from them directly, without needing an account. "opensea": { "privateReadOnlyApiKey": "..." } ### Tenor To enable searching for GIFs directly in the editor, provide an API key for [Tenor](https://tenor.com) . You can [request a Tenor API key](https://developers.google.com/tenor/guides/quickstart) from Google’s cloud console, for free. "tenor": { "googleApiKey": "..." } ### Twitter In order to display Twitter cards in newsletter emails, Ghost needs to be able to fetch data from the Twitter API and requires a Bearer Token to do so. You can [request Twitter API access](https://developer.twitter.com) from them via their developer portal. "twitter": { "privateReadOnlyToken": "..." } ### Pintura [Pintura](https://pqina.nl/pintura/) is an image editor that integrates with Ghost. After purchasing a license, upload the JS and CSS files via **Integrations** → **Pintura**. ![Ghost Pintura integration page](/images/docs/pintura-self-hosted_hubf952de862dc133080128958e1208795_480708_1397x0_resize_q100_h2_box_3.webp) ### Portal Ghost automatically loads the scripts for Portal from jsDelivr.net. The default configuration is shown below. The script can be relocated by changing the URL, or disabled entirely by setting `"url": false`. "portal": { "url": "https://cdn.jsdelivr.net/npm/@tryghost/portal@~{version}/umd/portal.min.js" } Ghost automatically loads the scripts & styles for search from jsDelivr.net. The default configuration is shown below. The script and stylesheet can be relocated by changing the URLs, or disabled entirely by setting `"url": false`. "sodoSearch": { "url": "https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@~{version}/umd/sodo-search.min.js", "styles": "https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@~{version}/umd/main.css" }, ### Comments Ghost automatically loads the scripts & styles for comments from jsDelivr.net. The default configuration is shown below. The script and stylesheet can be relocated by changing the URLs, or disabled entirely by setting `"url": false`. "comments": { "url": "https://cdn.jsdelivr.net/npm/@tryghost/comments-ui@~{version}/umd/comments-ui.min.js", "styles": "https://cdn.jsdelivr.net/npm/@tryghost/comments-ui@~{version}/umd/main.css" } Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Validate Ghost theme compatibility Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Validating your Ghost theme is handled efficiently with the GScan tool. GScan will check your theme for errors, deprecations and compatibility issues. GScan is used in several ways: * The [GScan site](https://gscan.ghost.org) is your first port of call to test any themes that you’re building to get a full validation report * When a theme is uploaded in Ghost admin, it will automatically be checked with `gscan` and any fatal errors will prevent the theme from being used * `gscan` is also used as a command line tool ### Command line To use GScan as a command line tool, globally install the `gscan` npm package: # Install the npm package npm install -g gscan # Use gscan anywhere to run gscan against a folder gscan /path/to/ghost/content/themes/casper # Run gscan on a zip file gscan -z /path/to/download/theme.zip Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Themes - Dynamic URLs & Routing Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Routing is the system that maps URL patterns to data and templates within Ghost. It comes pre-configured by default, but it can also be customized extensively to build powerful custom site structures. All of Ghost’s routing configuration is defined in `content/settings/routes.yaml` - which you can edit directly, but you can also upload/download this file from within your Ghost admin panel under `Settings » Labs`. If you edit the file manually, you’ll need to restart Ghost to see the changes, but if you upload the file in admin then your routes will automatically be updated right away. ### Base configuration The default `routes.yaml` which comes with all new installs of Ghost sets things up with a traditional publication structure. The homepage of the site is a reverse-chronological list of the site’s posts, with each post living on its own URL defined by a `{slug}` parameter, such as `my-great-post`. There are also additional archives of posts sorted by tag and author. ## routes.yaml routes: collections: /: permalink: /{slug}/ template: index taxonomies: tag: /tag/{slug}/ author: /author/{slug}/ For most publications and use cases, this structure is exactly what’s needed and it’s not necessary to make any edits in order to use Ghost or develop a theme for it. ### What’s YAML? YAML stands for **Y**et **A**nother **M**arkup **L**anguage - because there aren’t enough unfunny acronyms in computer science. You can think of it loosely like JSON without all the brackets and commas. In short, it’s a document format used to store nested `key:value` pairs, commonly used for simple/readable configuration. The most important thing to know when working with YAML is that it uses indentation to denote structure. That means the **only** type of nesting which works is **2 spaces**. The most common reason for YAML files not working is when you accidentally use the wrong type or quantity of spacing for indentation. So keep a close eye on that! ### When to use dynamic routing Maybe you want your homepage to be a simple landing page, while all of your posts appear on `site.com/writing/`. Maybe you actually want to split your site into two main collections of content, like `/blog/` and `/podcast/`. Maybe you just want to change the URL of your archives from `/tag/news/` to `/archive/news/`. If you’re looking to create an alternative site structure to the one described above, then dynamic routing is what you need in order to achieve all your hopes and dreams. Okay, maybe not all your hopes and dreams but at least your URLs. We’ll start there. Hopes and dreams come later. Custom Routes ------------- Template routes allow you to map individual URLs to specific template files within a Ghost theme. For example: make `/custom/` load `custom.hbs` Using template routes is very minimal. There’s no default data associated with them, so there isn’t any content automatically loaded in from Ghost like there is with posts and pages. Instead, you can write all the custom code you like into a specific file, and then have that file load on the route of your choice. Custom routes are handy for creating static pages outside of Ghost Admin, when you don’t want them to be editable, they use lots of custom code, or you need to create a specific custom URL with more than a basic slug. Don’t worry, we’ll go through some examples of all of the above! * * * ### Basic routing The [default routes.yaml file](/docs/themes/routing/) which comes with Ghost contains an empty section under `routes`, and this is where custom routes can be defined. Let’s say you’ve got a big **Features** landing page with all sorts of animations and custom HTML. Rather than trying to cram all the code into the Ghost editor and hope for the best, you can instead store the code in a custom template called `features.hbs` - and then point a custom route at it: routes: /features/: features The first half is the URL: `site.com/features/` - the second is the template which will be used: `features.hbs` - you leave off the `.hbs` because Ghost takes care of that part. Now you’ve created a new static page in Ghost, without using the admin! You can also use custom routes to simulate subdirectories. For example, if you want a “Team” page to appear, for navigational purposes, as if it’s a subpage of your “About” page. routes: /features/: features /about/team/: team Now `site.com/about/team/` is a dedicated URL for a static `team.hbs` template within your theme. Routes can be just about anything you like using letters, numbers, slashes, hyphens, and underscores. * * * ### Loading data The downside of using an `/about/team` route to point at a static `team.hbs` template is that it’s… well: static. Unlike the **Features** the team page needs to be updated fairly regularly with a list of team members; so it would be inconvenient to have to do that in code each time. What we really want is to keep the custom route, but have the page still use data stored in Ghost. This is where the `data` property comes in. routes: /features/: features /about/team/: template: team data: page.team This will assign all of the data from a Ghost **page** with a slug of `team` to the new route, and it will also automatically redirect the original URL of the content to the new one. Now, the data from `site.com/team/` will be available inside the `{{#page}}` block helper in a custom `team.hbs` template on `site.com/about/team/` and the old URL will redirect to the new one, to prevent the content being duplicated in two places. * * * ### Building feeds & APIs In the examples used so far, routes have been configured to generate a single page, some data and a template, but that’s not all routes can do. You can make a route output just about anything, for instance a custom RSS feed or JSON endpoint. If you create a custom template file with a [{{#get}}](/docs/themes/helpers/get/) helper API query loading a list of filtered posts, you can return those posts on a custom route with custom formatting. routes: /podcast/rss/: template: podcast-feed content_type: text/xml Generally, routes render HTML, but you can override that by specifying a `content_type` property with a custom mime-type. For example, you might want to build a custom RSS feed to get all posts tagged with `podcast` and deliver them to iTunes. In fact, [here’s a full tutorial](/docs/tutorials/custom-rss-feed/) for how to do that. Or perhaps you’d like to build your own little public JSON API of breaking news, and provide it to other people to be able to consume your most important updates inside their websites and applications? That’s fine too, you’d just pass `json` as the `content_type`. Collections ----------- Collections are the backbone of how posts on a Ghost site are organized, as well as what URLs they live on. You can think of collections as major sections of a site that represent distinct and separate types of content, for example: `blog` and `podcast`. **Collections serve two main purposes:** 1. To display all posts contained within them on a paginated index route 2. To determine the URL structure of their posts and where they ’live’ on the site. For this reason, posts can only ever be in **one** collection. A post must either be a blog or a podcast, it can’t be both. * * * ### The default collection The [default routes.yaml file](/docs/themes/routing/) which comes with Ghost contains just a single collection on the root `/` URL which defines the entire structure of the site. collections: /: permalink: /{slug}/ template: index Here, the home route of `site.com` will display all posts, using the `index.hbs` template file, and render each post on a URL determined by the `{slug}` created in the Ghost editor. In short: This is exactly how and why Ghost works by default! * * * ### Using a custom homepage One of the most minimal examples of editing the default collection is to move it to a new location and make room for a custom home page. routes: /: home collections: /blog/: permalink: /blog/{slug}/ template: index Using an example from the previous section on [custom routes](/docs/themes/routing/#routes) , the home `/` route is now pointing at a static template called `home.hbs` — and the main collection has now been moved to load on `site.com/blog/`. Each post URL is also prefixed with `/blog/`. * * * ### Filtering collections Much like the [{{#get}}](/docs/themes/helpers/get/) helper, collections can be filtered to contain only a subset of content on your site, rather than all of it. collections: /blog/: permalink: /blog/{slug}/ template: blog filter: primary_tag:blog /podcast/: permalink: /podcast/{slug}/ template: podcast filter: primary_tag:podcast Returning to the earlier example, all of the posts within Ghost here are divided into two collections of `blog` and `podcast`. #### Blog collection * **Appears on:** `site.com/blog/` * **Post URLs:** `site.com/blog/my-story/` * **Contains posts with:** a `primary_tag` of `blog` #### Podcast collection * **Appears on:** `site.com/podcast/` * **Post URLs:** `site.com/podcast/my-episode/` * **Contains posts with:** a `primary_tag` of `podcast` The `primary_tag` property is simply the _first_ tag that is entered in the tag list inside Ghost’s editor. It’s useful to filter against the **primary** tag because it will always be unique. If posts match the filter property for _multiple_ collections this can lead to problems with post rendering and collection pagination, so it’s important to try and always keep collection filters unique from one another. * * * ### Doing more with collections Collections are an incredibly powerful way to organize your content and your site structure, so its only limits are your imagination — and our clichés. #### Loading data into the index Much like [custom routes](/docs/themes/routing/#routes) , collections can also accept a data property in order to pass in the data to the collection’s index. For example, you might have a collection called `portfolio` which lists all of your most recent work. But how do you set the title, description, and metadata for _that_ collection index? collections: /portfolio/: permalink: /work/{slug}/ template: work filter: primary_tag:work data: tag.work Now, your `work.hbs` template will have access to all of the data (and metadata) from your `work` tag. And don’t forget: `site.com/tag/work/` will now also be redirected to `site.com/portfolio/` — so no duplicate content! #### Creating multi-lang sites Another really popular use for collections is for sites that publish content in multiple languages and want to create distinct areas and URL patterns for each locale. collections: /: permalink: /{slug}/ template: index filter: 'tag:-hash-de' /de/: permalink: /de/{slug}/ template: index-de filter: 'tag:hash-de' This would set the base URL to be in the site’s default language, and add an additional `site.com/de/` section for all posts in German, tagged with a private tag of `#de`. Using [Private tags](/help/organizing-content/#internal-tags) means these tags wouldn’t be shown on the front end but can still be treated differently with Handlebars templating. The main collection excludes these same posts to avoid any overlap. Taxonomies ---------- Taxonomies are groupings of posts based on a common relation. In Ghost, this is always defined by the post’s author or tag Using taxonomies, Ghost will automatically generate post archives for tags and authors like `/tag/getting-started/` which will render a list of associated content. Unlike [collections](/docs/themes/routing/#collections) , posts can appear in multiple taxonomies, and the post’s URL is not affected by which taxonomies are applied. Taxonomies are structured like this: taxonomies: tag: /tag/{slug}/ author: /author/{slug}/ If a post by `Cameron` is tagged with `News` then it will be included in archives appearing on: * `site.com` – (The collection index) * `site.com/author/cameron` * `site.com/tag/news/` Each of these comes with its own automatically generated RSS feeds that are accessed by adding /rss/ to the end of the URL. * * * ### Customising taxonomies The configuration options for taxonomies are a lot more basic than [routes](/docs/themes/routing/#routes) and [collections](/docs/themes/routing/#collections) . You can’t define new or custom taxonomies, you can only modify those which are already there and adapt their syntax a small amount. taxonomies: tag: /topic/{slug}/ author: /host/{slug}/ If you don’t like the prefixes for taxonomies, you can customize them to something else that suits your site and your content better. If you’re running a publication that is primarily a podcast, for example, you might prefer `host` and `topic`. * * * ### Removing taxonomies One small extra trick is that you can actually remove taxonomies entirely and not generate those pages for your site. If you prefer to keep things minimal, just leave the taxonomies field empty. taxonomies: ## Nothing but silence Just make sure you also update your template files to not link to any tag or author archives, which will now 404! Channels -------- If you want something more flexible than taxonomies, but less rigid than collections, then channels might be for you. A channel is a custom stream of paginated content matching a specific filter. This allows you to create subsets and supersets of content by combining or dividing existing posts into content hubs. Unlike [collections](/docs/themes/routing/#collections) , channels have no influence over a post’s URL or location within the site, so posts can belong to any number of channels. **The best way to think of channels is as a set of permanent search results.** It’s a filtered slice of content from across your site, without modifying the content itself. * * * ### Creating a channel Channels are defined as a [custom route](/docs/themes/routing/#routes) , with a custom `controller` property called `channel`, and a filter to determine which posts to return. routes: /apple-news/: controller: channel filter: tag:[iphone,ipad,mac] /editors-column/: controller: channel filter: tag:column+primary_author:cameron In this example, there are two channels. The first is a channel that will return any posts tagged `iPhone`, `iPad` or `Mac` on a custom route of `site.com/apple-news/`. The second is a special Editor’s Column area, which will return any posts tagged with `Column`, but only if they’re explicitly authored by `Cameron`. These are two small examples of how you can use channels to include and exclude groups of posts from appearing together on a custom paginated route, with full automatic RSS feeds included as standard. Just add `/rss/` to any channel URL to get the feed. * * * ### When to use channels vs collections Collections and channels share a lot of similarities because they’re both methods of filtering a set of posts and returning them on a custom URL. So how do you know when to use which? #### You should generally use a collection when… There’s a need to define permanent site structure and information architecture * **You’re sorting different types/formats of content** _eg. posts are blog posts OR podcasts_ * **You’re filtering incompatible content** _eg. posts are either in English OR German_ * **You want the parent filter to influence the post’s URL** _eg. an index page called `/news/` and posts like `/news/my-story/`_ #### You might be better off with a channel if… All you need is a computed view of a subsection of existing content * **You’re combining/grouping different pieces of content** _eg. posts tagged with `news` AND `featured`_ * **You’re dividing existing streams of content with multiple properties** _eg. posts tagged with `news` but NOT authored by `steve`_ * **You want to be able to update/change properties without affecting post URLs** _eg. quickly creating/destroying new sections of a site without any risk_ If you’re still not sure which is the best fit for you, drop by the [Ghost Forums](https://forum.ghost.org) and share what structure you’re hoping to accomplish. There’s a large community of Ghost developers around to help. Index of all available properties --------------------------------- | Property | Description | | --- | --- | | `template` | Determines which Handlebars template file will be used for this route. Defaults to `index.hbs` if not specified. | | `permalink` | The generated URL for any post within a collection. Can contain dynamic variables based on post data:

* `{id}` - unique set of characters, eg. `5982d807bcf38100194efd67`
* `{slug}` - the post slug, eg. `my-post`
* `{year}` - publication year, eg. `2019`
* `{month}` - publication month, eg. `04`
* `{day}` - publication day, eg. `29`
* `{primary_tag}` - slug of first tag listed in the post, eg. `news`
* `{primary_author}` - slug of first author, eg. `cameron` | | `filter` | Extensively filter posts returned in collections and channels using the full power and syntax of the [Ghost Content API](/docs/content-api/#filtering)


For example `author:cameron+tag:news` will return all posts published by Cameron, tagged with ‘News’. Mix and match to suit. | | `order` | Choose any number of fields and sort orders for your content:

* `published_at desc` - _default_, newest post first
* `published_at asc` - chronological, oldest first
* `featured desc, published_at desc` -
featured posts, then normal posts, newest first | | `data` | Fetch & associate data from the Ghost API with a specified route. The source route of the data will be redirected to the new custom route.

* `post.slug` - get data with => `{{#post}}`
* `page.slug` - get data with => `{{#page}}`
* `tag.slug` - get data with => `{{#tag}}`
* `author.slug` - get data with => `{{#author}}` | | `rss` | Collections and channels come with automatically generated RSS feeds which can be disabled by setting the `rss` property to `false` | | `content_type` | Specify the mime-type for the current route, default: `HTML` | | `controller` | Add a custom controller to a route to perform additional functions. Currently the only supported value is `channel` | Redirects --------- In addition to creating routes, you can also create redirects for any time there are any changes in your URLs and you need to forward visitors ### Accessing the redirects file > If you’ve updated your site from an earlier version (prior to 4.0), your redirects may be in JSON format. Both formats are still supported, but JSON support will be removed in a later version. The `redirects.yaml` file is located in `content/data/redirects.yaml` and - like `routes.yaml` - can also be downloaded/uploaded in the settings in Ghost Admin. ### File structure Refer to [Implementing redirects in Ghost](/docs/tutorials/implementing-redirects/) for more details about the file structure. ### Implementation Upload your new `redirects.yaml` file in Ghost admin in the settings. This is the recommended method. To replace the YAML file on the server, ensure it exists in `content/data/redirects.yaml` and restart Ghost for your changes to take effect. ### When not to use `redirects.yaml` There are some instances where it is not recommended to use the `redirects.yaml` file: * Page rules for www or HTTP/HTTPS redirection should always be implemented with your DNS provider. * Ghost automatically forces trailing slashes, so you do not need to write any page rules to accommodate for duplicate content caused by this. * If you’re trying to change the URL structure of your publication, the recommended way to do this is with dynamic routing and the `routes.yaml` file. (However, you may still need to redirect existing content using `redirects.yaml`). Final Tips ---------- Ghost’s dynamic routing system is an extremely powerful way to build advanced structures for your site, and it’s hard to document every possible example of what can be done with it in comprehensive detail. ### Detailed tutorials While these docs cover simple examples and broad use cases, you’ll find more detailed and specific use cases of how to build different types of publications in these tutorials: * [Make an iTunes Podcast RSS feed](https://ghost.org/tutorials/custom-rss-feed/) * [Use a custom homepage](https://ghost.org/tutorials/custom-homepage/) * [How to build specialized content hubs](https://ghost.org/tutorials/content-collections/) * [Define a custom order for posts](https://ghost.org/tutorials/change-post-order/) Head over to the [Ghost tutorials](/tutorials/) section to find even more tutorials about how to build different types of themes and websites with Ghost. * * * ### Limitations & troubleshooting As you work further with dynamic routing it’s worth keeping in mind that there are some limitations to what you’re able to do with it. Here are a few of the most common areas where you’ll find the edges of what’s possible: ##### Slugs can conflict Dynamic routing has no concept of what slugs are used in Ghost, and vice-versa. So if you create a route called `/about/` and a page in Ghost called `about` then one of them is going to work, but not both. You’ll need to manage this manually. ##### Collections must be unique If you have a collection filtering for posts tagged with `camera` and another filtering for posts tagged with `news` - then you will run into problems if a post is tagged with both `camera` and `news`. You should either trust your authors to use the correct tags, or base collections on properties that are always unique, like `primary_tag`. ##### Trailing slashes are required You probably noticed that all the examples here use trailing slashes on routes, which is because these are required for dynamic routing to function correctly. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost-CLI - A fully loaded tool for installation and configuration Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) A fully loaded tool to help you get Ghost installed and configured and to make it super easy to keep your Ghost install up to date. Ghost-CLI is to makes it possible to install or update Ghost with a _single command_. In addition, it performs useful operations to assist with maintaining your environment, such as: * Checking for common environment problems * Creating a **logical folder structure** * Providing for production or development installs * Allowing for **upgrades and rollbacks** * Handling **user management and permissions** * Configuring Ghost * Configuring **NGINX** * Setting up **MySQL** * Configuring **systemd** * Accessing Ghost log files * Managing existing Ghost installs * * * Install & update ---------------- Ghost-CLI is an npm module that can be installed via either npm. # On a production server using a non-root user: sudo npm install -g ghost-cli@latest Locally, you likely don’t need sudo. Using `@latest` means this command with either install or update ghost-cli and you only have to remember the one command for both ✨ Useful options -------------- There are some global flags you may find useful when using `ghost-cli`: # Output usage information for Ghost-CLI ghost --help, ghost -h, ghost help, ghost [command] --help # Enables the verbose logging output for debugging ghost --verbose, ghost -V # Print your CLI version and Ghost version ghost --version, ghost -v, ghost version # Run the command in a different directory ghost --dir path/to/directory # Runs command without asking for any input ghost --no-prompt # Runs command without using colours ghost --no-color Commands -------- Below are the available commands in Ghost-CLI. You can always run `ghost --help` or `ghost [command] --help` to get more detail, or inline help for available options. ### Ghost config `ghost config` accepts two optional arguments: `key` and `value`. Here are the three different combinations and what happens on each of them: # Create a new config file for the particular env ghost config # Find and return the value in the config for the key passed ghost config [key] # Set a key and a value in the config file ghost config [key] [value] # Set the url for your site ghost config url https://mysite.com The `ghost config` command only affects the configuration files. In order for your new config to be used, run `ghost restart`. #### Options If you’re using `ghost config` to generate a configuration file, you can supply multiple key-value pairs in the form of options to avoid being prompted for that value. All of these options can also be passed to `ghost install` and `ghost setup` , as these commands call `ghost config`. See the [config guide](/docs/config/) or run `ghost config --help` for more detailed information. ##### Application options # URL of the site including protocol --url https://mysite.com # Admin URL of the site --admin-url https://admin.mysite.com # Port that Ghost should listen on --port 2368 # IP to listen on --ip 127.0.0.1 # Transport to send log output to --log ["file","stdout"] ##### Database options # Type of database to use (SQLite3 or MySQL) --db # For SQLite3 we just need a path to database file --dbpath content/data/ghost_dev.db # For MySQL we need full credentials: --dbhost localhost # Database user name --dbuser ghost # Database password --dbpass **** # Database name --dbname ghost_dev ##### Mail options # Mail transport, E.g SMTP, Sendmail or Direct --mail SMTP # Mail service (used with SMTP transport), E.g. Mailgun, Sendgrid, Gmail, SES... --mailservice Mailgun # Mail auth user (used with SMTP transport) --mailuser postmaster@something.mailgun.org # Mail auth pass (used with SMTP transport) --mailpass **** # Mail host (used with SMTP transport) --mailhost smtp.eu.mailgun.org # Mail port (used with SMTP transport) --mailport 465 ##### Service options # Process manager to run with (local, systemd) --process local #### Debugging In order for your new config to be used, run `ghost restart`. * * * ### Ghost install The `ghost install` command is your one-stop-shop to get a running production install of Ghost. This command includes the necessary mysql, nginx and systemd configuration to get your publication online, and provides a series of setup questions to configure your new publication. The end result is a fully installed and configured instance ✨ > Not ready for production yet? `ghost install local` installs ghost in development mode using sqlite3 and a local process manager. Read more about [local installs](/docs/install/local/) > . #### How it works The `ghost install` command runs a nested command structure, but you only ever have to enter a single command. First, it will run `ghost doctor` to check your environment is compatible. If checks pass, a local folder is setup, and Ghost is then downloaded from npm and installed. Next, `ghost setup` runs, which will provide [prompts](/docs/install/ubuntu/#install-questions) for you to configure your new publication via the `ghost config` command, including creating a MySQL user, initialising a database, configure nginx and sets up SSL. Finally, the CLI will prompt to see if you want to run Ghost and if you choose yes `ghost start` will run. #### Arguments # Install a specific version (1.0.0 or higher) ghost install [version] # Install version 2.15.0 ghost install 2.15.0 # Install locally for development ghost install local # Install version 2.15.0, locally for development ghost install 2.15.0 --local #### Options As `ghost install` runs nested commands, it also accepts options for the `ghost doctor`, `ghost config`, `ghost setup` and `ghost start` commands. See the individual command docs, or run `ghost install --help` for more detailed information. # Get more information before running the command ghost install --help # Install in development mode for a staging env ghost install --development, ghost install -D # Select the directory to install Ghost in ghost install --dir path/to/dir # Install Ghost from a specific archive (useful for testing or custom builds) ghost install --archive path/to/file.tgz # Disable stack checks ghost install --no-stack # Install without running setup ghost install --no-setup # Install without starting Ghost ghost install --no-start # Tells the process manager not to restart Ghost on server reboot ghost setup --no-enable # Install without prompting (disable setup, or pass all required parameters as arguments) ghost install --no-prompt #### Directory structure When you install Ghost using Ghost-CLI, the local directory will be setup with a set of folders designed to keep the various parts of your install separate. After installing Ghost, you will have a folder structure like this which should not be changed: . ├── .config.[env].json # The config file for your Ghost instance ├── .ghost-cli # Utility system file for Ghost CLI, don't modify ├── /content # Themes/images/content, not changed during updates ├── /current # A symlink to the currently active version of Ghost ├── /system # NGINX/systemd/SSL files on production installs └── /versions # Installed versions of Ghost available roll forward/back to * * * ### Ghost setup `ghost setup` is the most useful feature of Ghost-CLI. In most cases you will never need to run it yourself, as it’s called automatically as a part of `ghost install`. #### How it works Setup configures your server ready for running Ghost in production. It assumes the [recommended stack](/docs/install/ubuntu/#prerequisites/) and leaves your site in a production-ready state. Setup is broken down into stages: * **mysql** - create a specific MySQL user that is used only for talking to Ghost’s database. * **nginx** - creates an nginx configuration * **ssl** - setup SSL with letsencrypt, using [acme.sh](https://github.com/Neilpang/acme.sh) * **migrate** - initialises the database * **linux-user** - creates a special low-privilege `ghost` user for running Ghost #### What if I want to do something else? The `Ghost-CLI` tool is designed to work with the recommended stack and is the only supported install method. However, since Ghost is a fully open-source project, and many users have different requirements, it is possible to setup and configure your site manually. The CLI tool is flexible and each stage can be run individually by running `ghost setup ` or skipped by passing the `--no-setup-` flag. #### Arguments # Run ghost setup with specific stages ghost setup [stages...] # Creates a new mysql user with minimal privileges ghost setup mysql # Creates an nginx config file in `./system/files/` and adds a symlink to `/etc/nginx/sites-enabled/` ghost setup nginx # Creates an SSL service for Ghost ghost setup ssl # Create an nginx and ssl setup together ghost setup nginx ssl # Creates a low-privileged linux user called `ghost` ghost setup linux-user # Creates a systemd unit file for your site ghost setup systemd # Runs a database migration ghost setup migrate #### Options As `ghost setup` runs nested commands, it also accepts options for the `ghost config`, `ghost start` and `ghost doctor` commands. Run `ghost setup --help` for more detailed information. # Skips a setup stage ghost setup --no-setup-mysql ghost setup --no-setup-nginx ghost setup --no-setup-ssl ghost setup --no-setup-systemd ghost setup --no-setup-linux-user ghost setup --no-setup-migrate # Configure a custom process name should be (default: ghost-local) ghost setup --pname my-process # Disable stack checks ghost setup --no-stack # Setup without starting Ghost ghost setup --no-start # Tells the process manager not to restart Ghost on server reboot ghost setup --no-enable # Install without prompting (must pass all required parameters as arguments) ghost setup --no-prompt * * * ### Ghost start Running `ghost start` will start your site in background using the configured process manager. The default process manager is **systemd**, or local for local installs. The command must be executed in the directory where the Ghost instance you are trying to start lives, or passed the correct directory using the `--dir` option. #### Options # Start running the Ghost instance in a specific directory ghost start --dir /path/to/site/ # Start ghost in development mode ghost start -D, ghost start --development # Tells the process manager to restart Ghost on server reboot ghost start --enable # Tells the process manager not to restart Ghost on server reboot ghost start --no-enable # Disable memory availability checks in ghost doctor ghost start --no-check-mem #### Debugging If running `ghost start` gives an error, try use `ghost run` to start Ghost without using the configured process manager. This runs Ghost directly, similar to `node index.js`. All the output from Ghost will be written directly to your terminal, showing up any uncaught errors or other output that might not appear in log files. * * * ### Ghost stop Running `ghost stop` stops the instance of Ghost running in the current directory. Alternatively it can be passed the name of a particular ghost instance or directory. You can always discover running Ghost instances using `ghost ls`. #### Arguments # Stop Ghost in the current folder ghost stop # Stop a specific Ghost instance (use ghost ls to find the name) ghost stop [name] # Stop the Ghost instance called ghost-local ghost stop ghost-local #### Options # Stop all running Ghost instances ghost stop --all # Stop running the Ghost instance in a specific directory ghost stop --dir /path/to/site/ # Tells the process manager that Ghost should not start on server reboot ghost stop --disable * * * ### Ghost restart Running `ghost restart` will stop and then start your site using the configured process manager. The default process manager is systemd, or local for local installs. The command must be executed in the directory where the Ghost instance you are trying to start lives, or passed the correct directory using the `--dir` option. #### Options # Start running the Ghost instance in a specific directory ghost restart --dir /path/to/site/ #### Debugging If running `ghost restart` gives an error, try using `ghost run` to debug the error. * * * ### Ghost update Run `ghost update` to upgraded to new versions of Ghost, which are typically released every 1-2 weeks. #### Arguments # Update to the latest version ghost update # Update to a specific version (1.0.0 or higher) ghost update [version] # Update to version 2.15.0 ghost update 2.15.0 #### Options # If an upgrade goes wrong, use the rollback flag ghost update --rollback # Install and re-download the latest version of Ghost ghost update --force # Force install a specific version of Ghost ghost update [version] --force # Updates to the latest within v1 ghost update --v1 # Don't restart after upgrading ghost update --no-restart # Disable the automatic rollback on failure ghost update --no-auto-rollback # Upgrade Ghost from a specific zip (useful for testing or custom builds) ghost update --zip path/to/file.zip # Disable memory availability checks in ghost doctor ghost update --no-check-mem #### Major updates Every 12-18 months we release a [major version](/docs/faq/major-versions-lts/) which breaks backwards compatibility and requires a more involved upgrade process, including backups and theme compatibility. Use the [update documentation](/docs/update/) as a guide to the necessary steps for a smooth upgrade experience. #### Debugging If running `ghost update` gives an error, try using `ghost run` to debug the error. * * * ### Ghost backup Run `ghost backup` to generate a zip file backup of your site data. #### How it works When performing manual updates it’s recommended to make frequent backups, so if anything goes wrong, you’ll still have all your data. This is especially important when [updating](/docs/update/) to the latest major version. This command creates a full backup of your site data, including: * Your content in JSON format * A full member CSV export * All themes that have been installed including your current active theme * Images, files, and media (video and audio) * A copy of `routes.yaml` and `redirects.yaml` or `redirects.json` Read more about how to [manually download your site data](/docs/faq/manual-backup/) . * * * ### Ghost doctor Running `ghost doctor` will check the system for potential hiccups when installing or updating Ghost. This command allows you to use `ghost-cli` as a diagnostic tool to find potential issues for your Ghost install, and provides information about what needs to be resolved if any issues arise. The CLI automatically runs this command when installing, updating, starting or setting up ghost - and you can use is manually with `ghost doctor`. #### Arguments # Check is the required config file exists and validates it's values ghost doctor startup # Check if the setup process was successful ghost doctor setup #### Options Run `ghost doctor --help` for more detailed information. # Disable the memory availability checks ghost doctor --no-check-mem * * * ### Ghost ls The `ghost ls` command lists all Ghost sites and their status from the `~/.ghost/config` file. This is useful if you can’t remember where you installed a particular instance of Ghost, or are working with multiple instances (local, production, staging and so on). #### Output # Development > ghost ls ┌────────────────┬─────────────────────────────────┬─────────┬─────────────────────-─┬─────┬──────-┬─────────────────┐ │ Name │ Location │ Version │ Status │ URL │ Port │ Process Manager │ ├────────────────┼─────────────────────────────────┼─────────┼─────────────────────-─┼─────┼──────-┼─────────────────┤ │ ghost-local │ ~/Sites/cli-test │ 1.22.1 │ stopped │ n/a │ n/a │ n/a │ ├────────────────┼─────────────────────────────────┼─────────┼─────────────────────-─┼─────┼──────-┼─────────────────┤ │ ghost-local-2 │ ~/Sites/theme-dev │ 2.12.0 │ stopped │ n/a │ n/a │ n/a │ ├────────────────┼─────────────────────────────────┼─────────┼─────────────────────-─┼─────┼──────-┼─────────────────┤ │ ghost-local-3 │ ~/Sites/new-theme │ 2.20.0 │ running (development) │ │ 2368 │ local │ └────────────────┴─────────────────────────────────┴─────────┴──────────────────────-┴─────┴─────-─┴─────────────────┘ # Production > ghost ls + sudo systemctl is-active ghost_my-ghost-site ┌───────────────┬────────────────┬─────────┬──────────────────────┬─────────────────────────--┬──────┬─────────────────┐ │ Name │ Location │ Version │ Status │ URL │ Port │ Process Manager │ ├───────────────┼────────────────┼─────────┼──────────────────────┼─────────────────────────--┼──────┼─────────────────┤ │ my-ghost-site │ /var/www/ghost │ 2.1.2 │ running (production) │ https://my-ghost-site.org │ 2368 │ systemd │ └───────────────┴────────────────┴─────────┴──────────────────────┴─────────────────────────--┴──────┴─────────────────┘ * * * ### Ghost log View the access and error logs from your Ghost site (not the CLI). By default `ghost log` outputs the last 20 lines from the access log file for the site in the current folder. Ghost’s default log config creates log files in the `content/logs` directory, and creates two different files: 1. An **access log** that contains all log levels, named e.g. `[site_descriptor].log` 2. An **error log** that contains error-level logs _only_, named e.g. `[site_descriptor].error.log` The site descriptor follows the pattern `[proto]__[url]__[env]` e.g. `http__localhost_2368__development` or `https__mysite_com__production`. The files are be rotated, therefore you may see many numbered files in the `content/logs` directory. #### Arguments # View last 20 lines of access logs ghost log # View logs for a specific Ghost instance (use ghost ls to find the name) ghost log [name] # View logs for the Ghost instance called ghost-local ghost log ghost-local #### Options # Show 100 log lines ghost log -n 100, ghost log --number 100 # Show only the error logs ghost log -e, ghost log --error # Show 50 lines of the error log ghost log -n 50 -e # Follow the logs (e.g like tail -f) ghost log -f, ghost log --follow # Follow the error log ghost log -fe # Show logs for the Ghost instance in a specific directory ghost log --dir /path/to/site/ #### Debugging There may be some output from Ghost that doesn’t appear in the log files, so for debugging purposes you may also want to try the [ghost run](/docs/ghost-cli#ghost-run) command. If you have a custom log configuration the `ghost log` command may not work for you. In particular the `ghost log` command requires that file logging is enabled. See the [logging configuration docs](/docs/config/#logging) for more information. * * * ### Ghost uninstall **Use with caution** - this command completely removes a Ghost install along with all of its related data and config. There is no recovery from this if you have no backups. The command `ghost uninstall` must be executed in the directory containing the Ghost install that you would like to remove. The following tasks are performed: * stop ghost * disable systemd if necessary * remove the `content` folder * remove any related systemd or nginx configuration * remove the remaining files inside the install folder > Running `ghost uninstall --no-prompt` or `ghost uninstall --force` will skip the warning and remove Ghost without a prompt. * * * ### Ghost help Use the help command to access a list of possible `ghost-cli` commands when required. This command is your port of call when you want to discover a list of available commands in the Ghost-CLI. You can call it at any time ✨ #### Output Commands: ghost buster Who ya gonna call? (Runs `yarn cache clean`) ghost config [key] [value] View or edit Ghost configuration ghost doctor [categories..] Check the system for any potential hiccups when installing/updating Ghost ghost install [version] Install a brand new instance of Ghost ghost log [name] View the logs of a Ghost instance ghost ls View running ghost processes ghost migrate Run system migrations on a Ghost instance ghost restart Restart the Ghost instance ghost run Run a Ghost instance directly (used by process managers and for debugging) ghost setup [stages..] Setup an installation of Ghost (after it is installed) ghost start Start an instance of Ghost ghost stop [name] Stops an instance of Ghost ghost uninstall Remove a Ghost instance and any related configuration files ghost update [version] Update a Ghost instance ghost version Prints out Ghost-CLI version (and Ghost version if one exists) Global Options: --help Show help [boolean] -d, --dir Folder to run command in -D, --development Run in development mode [boolean] -V, --verbose Enable verbose output [boolean] --prompt [--no-prompt] Allow/Disallow UI prompting [boolean] [default: true] --color [--no-color] Allow/Disallow colorful logging [boolean] [default: true] --auto Automatically run as much as possible [boolean] [default: false] #### Options It’s also possible to run `ghost install --help` and `ghost setup --help` to get a specific list of commands and help for the install and setup processes. Don’t worry - you got this! 💪 * * * Knowledgebase ------------- ### SSL The CLI generates a free SSL certificate from [Let’s Encrypt](#lets-encrypt) using [acme.sh](#lets-encrypt) and a secondary NGINX config file to serve https traffic via port 443. ##### SSL configuration After a successful ssl setup, you can find your ssl certificate in `/etc/letsencrypt`. ##### SSL for additional domains You may wish to have multiple domains that redirect to your site, e.g. to have an extra TLD or to support www. domains. **Ghost itself can only ever have one domain pointed at it.** This is intentional for SEO purposes, however you can always redirect extra domains to your Ghost install using nginx. If you want to redirect an HTTPS domain, you must have a certificate for it. If you want to use Ghost-CLI to generate an extra SSL setup, follow this guide: # Determine your secondary URL ghost config url https://my-second-domain.com # Get Ghost-CLI to generate an SSL setup for you: ghost setup nginx ssl # Change your config back to your canonical domain ghost config url https://my-canonical-domain.com # Edit the nginx config files for your second domain to redirect to your canonical domain. In both files replace the content of the first location block with: return 301 https://my-canonical-domain.com$request_uri; # Get nginx to verify your config sudo nginx -t # Reload nginx with your new config sudo nginx -s reload ##### Let’s Encrypt [Let’s Encrypt](https://letsencrypt.org/) provides SSL certificates that are accepted by browsers free of charge! This is provided by the non-profit Internet Security Research Group (ISRG). The Ghost-CLI will offer you to generate a free SSL certificate as well as renew it every 60 days. Ghost uses [acme.sh](https://github.com/Neilpang/acme.sh) for provisioning and renewing SSL certificates from Let’s Encrypt. You can call `acme.sh` manually if you need to perform extra tasks. The following command will output all available options: /etc/letsencrypt/acme.sh --home "/etc/letsencrypt" ### Systemd `systemd` is the default way of starting and stopping applications on Ubuntu. The advantage is that if Ghost crashes, `systemd` will restart your instance. This is the default recommended process manager. ### Permissions Ghost-CLI will create a new system user and user-group called `ghost` during the installation process. The `ghost` user will be used to run your Ghost process in `systemd`. This means that Ghost will run with a user that has no system-wide permissions or a shell that can be used (similar to other services such as NGINX). Sudo is required to modify files in the The `/content/`. To prevent accidental permissions changes, it’s advisable to execute tasks such as image upload or theme upload using Ghost admin. #### File Permissions The `ghost-cli` enforces default linux permissions (via `ghost doctor` hooks) for installations. * For normal users, default directory permissions are 775, and default file permissions are 664. * For root users, default directory permissions are 755, and default file permissions are 644. Running ghost install as the non-root user will result in directories created with 775 (`drwxrwxr-x`) permissions and file with 664 (`-rw-rw-r--`) permissions. These file permissions don’t need to be changed. The only change that is executed by ghost-cli is changing ownership, file permissions stay untouched. If permissions were changed, the following two commands will revert file and directory permissions to the ones of a non-root user. sudo find /var/www/ghost/* -type d -exec chmod 775 {} \; sudo find /var/www/ghost/* -type f -exec chmod 664 {} \; The cli doesn’t support directory flags such as `setuid` and `setguid`). If your commands keep failing because of file permissions, ensure your directories have no flags! Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to use custom settings in Ghost themes - Developer docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Custom theme settings are a powerful tool that allows theme developers to configure custom settings that appear in Ghost Admin — making it easy for site owners to make stylistic choices without needing to edit theme files. Overview -------- Custom theme settings are specified by the theme developer in the `package.json` file at the `config.custom` key, and there are five types of custom theme settings available: * `select` * `boolean` * `color` * `image` * `text` { "config": { "custom": { "typography": { "type": "select", "options": ["Modern sans-serif", "Elegant serif"], "default": "Modern sans-serif" }, "cta_text": { "type": "text", "default": "Sign up for more like this", "group": "post" } } } } Once defined in the `package.json` file, custom settings can be accessed in Handlebars templates using the `@custom` object. ... Themes are limited to a total of 20 custom settings. See the [usage guidelines](#guidelines-for-theme-developers) for details on the most effective ways to use custom settings. Setting keys/names ------------------ The key given to each setting is used as the display name in Ghost Admin, and as the property name on the `@custom` object. { "config": { "custom": { "cta_text": { "type": "text", "default": "Sign up for more like this", "group": "post", "description": "Used in a large CTA on the homepage and small one on the sidebar as well" } } } } In this example, the `"cta_text"` key is displayed to site owners as **CTA Text** and can be referenced in Handlebars templates using `@custom.cta_text`. ![Text Custom Setting](/images/docs/themes/setting-type-text_hu878685915f7c6b65155992b4a20a3eac_25477_1644x0_resize_q100_h2_box_3.webp) Setting keys must be all lowercase with no special characters and in `snake_case` where each space is represented by an `_`. Changing a setting’s key when releasing a new theme version is a breaking change for site owners who upgrade from an older version. The setting with the old key is removed, losing any value entered by the site owner, and a new setting with the current key is created with its default value. Setting groups -------------- Theme settings fall under the **Theme** tab in **Design & branding**, and are grouped into one of three categories: * Site wide * Homepage * Post ![Setting groups](/images/docs/themes/setting-groups_hu9b3d4ebb234056755f3680e4fdc54d1b_2122069_4096x0_resize_q100_h2_box_3.webp) By default, all custom settings appear in the **Site wide** category. Custom settings that are specific to the homepage or post display are defined with an optional `"group"` property with the value `"homepage"` or `"post"`. { "config": { "custom": { "typography": { "type": "select", "options": ["Modern sans-serif", "Elegant serif"], "default": "Modern sans-serif", "description": "Define the default font used for the publication" }, "feed_layout": { "type": "select", "options": ["Dynamic grid", "Simple grid", "List"], "default": "Dynamic grid", "group": "homepage", "description": "The layout of the post feed on the homepage, tag, and author pages" }, "cta_text": { "type": "text", "default": "Sign up for more like this", "group": "post", "description": "Used in a large CTA on the homepage and small one on the sidebar as well" } } } } Settings should be organized into groups that will make sense for site owners based on your usage of the setting in the theme. Setting a description --------------------- Give users more information about what a custom setting does by providing a short description. The description will appear along with the setting in Ghost admin. Description must be fewer than 100 characters. Setting types ------------- Each of the five custom setting types has particular fields and requirements. All custom settings require a valid `"type"` — an unknown type causes a theme validation error. ### Select Presents a select input with options defined by the theme developer. Select settings are used to offer site owners multiple predefined options in combination with the `match` helper: "feed_layout": { "type": "select", "options": ["Dynamic grid", "Simple grid", "List"], "default": "Dynamic grid" } {{#match @custom.feed_layout "Dynamic grid"}} // {{/match}} ![Select Custom Setting](/images/docs/themes/setting-type-select_hu66e48d0a12b84f5270a886c24dc079fa_5437_1876x0_resize_q100_h2_box_3.webp) #### Validation * `options` is required and must be an array of strings * `default` is required and must match one of the defined options ### Boolean Presents a checkbox toggle. "recent_posts": { "type": "boolean", "default": true } ![Boolean Custom Setting](/images/docs/themes/setting-type-boolean_hu2ded47e2af98aad7bab23dbc5e0941ba_19117_1644x0_resize_q100_h2_box_3.webp) #### Validation * `default` is required and must be either `true` or `false` Boolean settings can simply be used with the `{{#if}}` helper: {{#if @custom.recent_posts}} // {{/if}} ### Color Presents a color picker. "button_color": { "type": "color", "default": "#15171a" } ![Color Custom Setting](/images/docs/themes/setting-type-color_hue94a37f604d47f4aebf527f78c55a34e_28164_1644x0_resize_q100_h2_box_3.webp) #### Validation * `default` is required and must be a valid hexadecimal string Use the color setting value in the theme by accessing the custom setting directly. ### Image Presents an image uploader. When output in themes, the value will be blank or a URL. "cta_background_image": { "type": "image" } ![Image Custom Setting](/images/docs/themes/setting-type-image_huefef4eb9ddaf1abc8b7f625866424bc8_24847_1644x0_resize_q100_h2_box_3.webp) #### Validation * `default` is not allowed Use the image setting value in the theme by directly accessing the setting, or use with the `{{img_url}}` helper. You can pass in dynamic image sizes, if you would like to output the image in question at a resized resolution based on your theme config. // or ### Text Presents a text input. The value may be blank or free-form text. "cta_text": { "type": "text", "default": "Sign up for more like this." } ![Text Custom Setting](/images/docs/themes/setting-type-text_hu878685915f7c6b65155992b4a20a3eac_25477_1644x0_resize_q100_h2_box_3.webp) #### Validation * `default` is optional Remember to allow a use case with no text. For example, this link will only be displayed if text has been provided: {{#if @custom.cta_text}} {{@custom.cta_text}} {{/if}} Fallback settings ----------------- Regardless of the Ghost version, themes providing custom settings shouldn’t look broken, and should provide a fallback when necessary. ### Creating fallbacks for text settings The default text for a text setting should be specified in `package.json` instead of adding it in the theme code as a fallback. This allows your theme to handle blank strings in the correct way: "cta_text": { "type": "text", "default": "Sign up now." } {{#if @custom.cta_text}}

{{@custom.cta_text}}

{{/if}} The only exception is when the theme **must** have text for a specific setting. In this situation, the default should be added in the theme as a fallback with an `{{else}}` statement:

{{#if @custom.copyright_text_override}} {{@custom.copyright_text_override}} {{else}} {{@site.title}} © {{date format="YYYY"}} {{/if}}

Setting visibility ------------------ Configure setting dependencies to ensure that only relevant settings are displayed to the user in Ghost Admin. For example, a theme may offer several different header styles: `Landing`, `Highlight`, `Magazine`, `Search`, `Off`. If that value is `Landing` or `Search`, then an additional option becomes visible in Ghost Admin that allows the use of the publication’s cover image as the background. Otherwise, the option is hidden. By configuring setting dependencies, users get a better experience by only seeing settings that are relevant. To control when settings are visible, include the `visibility` key on the dependent setting. This key specifies the conditions that must be met for the setting to be displayed. Typically, you’ll specify the name of the parent setting and value it should have for the dependent setting to be visible. You can also use any [NQL syntax](https://ghost.org/docs/content-api/#filtering) for this — the same syntax used for filtering with the `get` helper. **Example: Header style and background image** In the following example, the `use_publication_cover_as_background` is only visible when `header_style` is `Landing` or `Search`. Note that when the visibility condition isn’t met, the dependent setting will render as `null` in the theme (i.e., `@custom.use_publication_cover_as_background` will be `null`). { "header_style": { "type": "select", "options": [\ "Landing",\ "Highlight",\ "Magazine",\ "Search",\ "Off"\ ], "default": "Landing", "group": "homepage" }, "use_publication_cover_as_background": { "type": "boolean", "default": false, "description": "Cover image will be used as a background when the header style is Landing or Search", "group": "homepage", "visibility": "header_style:[Landing, Search]" } } **Example: Post feed style and thumbnails** In this example, the `show_images_in_feed` setting is only visible when `post_feed_style` is set to `List`. { "post_feed_style": { "type": "select", "options": [\ "List",\ "Grid"\ ], "default": "List", "group": "homepage" }, "show_images_in_feed": { "type": "boolean", "default": true, "description": "Toggles thumbnails of the post cards when the post feed style is List", "group": "homepage", "visibility": "post_feed_style:List" } } Setting up support for custom fonts ----------------------------------- Custom fonts allow users to select heading and body fonts for their themes from a curated list. This provides the user with a broad range of font styles so your theme can appeal to a wider audience. ![Custom fonts](/images/docs/themes/custom-fonts_hue91cf81b8a9b6eaec2ca5d2661927ae4_1137163_2000x0_resize_q100_h2_box_3.webp) If you’d like to give users the possibility to select custom fonts, you’ll need make sure your theme supports it. ### How custom fonts are loaded When a custom font is selected, Ghost loads the font files on the front-end via `{{ghost_head}}` and sets up two CSS variables that reference them: ### Applying custom font variables To use custom fonts in your theme, apply the provided variables within your theme’s CSS file: Selected font names are also injected into `{{body_class}}`, allowing you to optionally fine-tune and make adjustments to any font: ... ### Setting fallbacks to your theme’s own font(s) If custom fonts aren’t set, you can provide a fallback to your theme’s own font(s): Check out any of our official themes (e.g. [Source](https://github.com/Tryghost/Source) ) to see it in action. Guidelines for theme developers ------------------------------- #### Custom settings should compliment the primary use case of the theme Ghost Themes should always have a very **clear use case** and the implementation of custom settings should compliment that use case. For example, a theme that is designed for newsletters may have custom settings to make visual changes to button colors and typography, but shouldn’t include custom settings to turn the theme into a magazine layout. ✅ **Simple visual changes** — give site owners the ability to create a great visual impact without altering the primary use-case of the theme. For example, changing colors, fonts and images. ❌ **Complex layout settings** — using custom settings to alter the primary use case of the theme results in complicated code that is harder to manage in the future. #### Custom settings should have a very clear visual impact Custom settings are designed to allow site owners to make meaningful customizations to their theme, without needing to edit theme files or inject code. **The total number of settings is limited to 20!** Use your custom settings wisely to give publishers the tools they need to define the best visual fit for their brand. ✅ **Visual brand settings** — use custom settings to make brand adjustments that have a visual impact, such as changing the color of all buttons, changing the default CTA text on the homepage, or offering a dark mode toggle. ❌ **Repeated settings** — avoid using custom settings to make micro-adjustments to single elements of a theme, such as individual buttons. ❌ **Functional settings** — avoid using custom settings to change the way a theme functions, such as changing the pagination style, or removing the primary tag from posts — these are functional settings that should be determined based on the primary use case of the theme. #### Using custom settings for external integrations It’s possible to use custom settings to enable third-party integrations within your theme, such as commenting systems or website analytics. To use custom settings for this purpose, site owners should be asked to enter a simple piece of information such as a tracking ID, rather than adding HTML code into a custom text setting. ✅ Enter a Disqus shortname into a custom setting, and enabling the comment system only when the shortname is provided ✅ Enter a tracking ID into a custom setting, and enabling Google Analytics only when the ID is provided ❌ Ask users to add an embed code into custom settings to make an integration function. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Content API Documentation Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost’s RESTful Content API delivers published content to the world and can be accessed in a read-only manner by any client to render in a website, app, or other embedded media. Access control is managed via an API key, and even the most complex filters are made simple with our SDK. The Content API is designed to be fully cachable, meaning you can fetch data as often as you like without limitation. * * * API Clients ----------- ### JavaScript Client Library We’ve developed an [API client for JavaScript](/docs/content-api/javascript/) that will allow you to quickly and easily interact with the Content API. The client is an advanced wrapper on top of our REST API - everything that can be done with the Content API can be done using the client, with no need to deal with the details of authentication or the request & response format. * * * URL --- `https://{admin_domain}/ghost/api/content/` Your admin domain can be different to your site domain. Using the correct domain and protocol are critical to getting consistent behaviour, particularly when dealing with CORS in the browser. All Ghost(Pro) blogs have a `*.ghost.io domain` as their admin domain and require https. ### Key `?key={key}` Content API keys are provided via a query parameter in the URL. These keys are safe for use in browsers and other insecure environments, as they only ever provide access to public data. Sites in private mode should consider where they share any keys they create. Obtain the Content API URL and key by creating a new `Custom Integration` under the **Integrations** screen in Ghost Admin. ![Get a Ghost Content API key](/images/docs/apikey_huc23d3a1fbe859434094a9db94f574d9a_265920_2920x0_resize_q100_h2_box_3.webp) ### Accept-Version Header `Accept-Version: v{major}.{minor}` Use the `Accept-Version` header to indicate the minimum version of Ghost’s API to operate with. See [API Versioning](/docs/faq/api-versioning/) for more details. ### Working Example # cURL # Real endpoint - copy and paste to see! curl -H "Accept-Version: v5.0" "https://demo.ghost.io/ghost/api/content/posts/?key=22444f78447824223cefc48062" * * * Endpoints --------- The Content API provides access to Posts, Pages, Tags, Authors, Tiers, and Settings. All endpoints return JSON and are considered [stable](/docs/faq/api-versioning/) . ### Working Example | Verb | Path | Method | | --- | --- | --- | | GET | [/posts/](#posts) | Browse posts | | GET | [/posts/{id}/](#posts) | Read a post by ID | | GET | [/posts/slug/{slug}/](#posts) | Read a post by slug | | GET | [/authors/](#authors) | Browse authors | | GET | [/authors/{id}/](#authors) | Read an author by ID | | GET | [/authors/slug/{slug}/](#authors) | Read a author by slug | | GET | [/tags/](#tags) | Browse tags | | GET | [/tags/{id}/](#tags) | Read a tag by ID | | GET | [/tags/slug/{slug}/](#tags) | Read a tag by slug | | GET | [/pages/](#pages) | Browse pages | | GET | [/pages/{id}/](#pages) | Read a page by ID | | GET | [/pages/slug/{slug}/](#pages) | Read a page by slug | | GET | [/tiers/](#tiers) | Browse tiers | | GET | [/settings/](#settings) | Browse settings | The Content API supports two types of request: Browse and Read. Browse endpoints allow you to fetch lists of resources, whereas Read endpoints allow you to fetch a single resource. * * * Resources --------- The API will always return valid JSON in the same structure: { "resource_type": [{\ ...\ }], "meta": {} } * `resource_type`: will always match the resource name in the URL. All resources are returned wrapped in an array, with the exception of `/site/` and `/settings/`. * `meta`: contains [pagination](/docs/content-api/#pagination) information for browse requests. ### Posts Posts are the primary resource in a Ghost site. Using the posts endpoint it is possible to get lists of posts filtered by various criteria. GET /content/posts/ GET /content/posts/{id}/ GET /content/posts/slug/{slug}/ By default, posts are returned in reverse chronological order by published date when fetching more than one. The most common gotcha when fetching posts from the Content API is not using the [include](#include) parameter to request related data such as tags and authors. By default, the response for a post will not include these: { "posts": [\ {\ "slug": "welcome-short",\ "id": "5ddc9141c35e7700383b2937",\ "uuid": "a5aa9bd8-ea31-415c-b452-3040dae1e730",\ "title": "Welcome",\ "html": "

👋 Welcome, it's great to have you here.

",\ "comment_id": "5ddc9141c35e7700383b2937",\ "feature_image": "https://static.ghost.org/v3.0.0/images/welcome-to-ghost.png",\ "feature_image_alt": null,\ "feature_image_caption": null,\ "featured": false,\ "visibility": "public",\ "created_at": "2019-11-26T02:43:13.000+00:00",\ "updated_at": "2019-11-26T02:44:17.000+00:00",\ "published_at": "2019-11-26T02:44:17.000+00:00",\ "custom_excerpt": null,\ "codeinjection_head": null,\ "codeinjection_foot": null,\ "custom_template": null,\ "canonical_url": null,\ "url": "https://docs.ghost.io/welcome-short/",\ "excerpt": "👋 Welcome, it's great to have you here.",\ "reading_time": 0,\ "access": true,\ "og_image": null,\ "og_title": null,\ "og_description": null,\ "twitter_image": null,\ "twitter_title": null,\ "twitter_description": null,\ "meta_title": null,\ "meta_description": null,\ "email_subject": null\ }\ ] } Posts allow you to include `authors` and `tags` using `&include=authors,tags`, which will add an `authors` and `tags` array to the response, as well as both a `primary_author` and `primary_tag` object. #### Working Example # cURL # Real endpoint - copy and paste to see! curl "https://demo.ghost.io/ghost/api/content/posts/?key=22444f78447824223cefc48062&include=tags,authors" Returns: { "posts": [\ {\ "slug": "welcome-short",\ "id": "5c7ece47da174000c0c5c6d7",\ "uuid": "3a033ce7-9e2d-4b3b-a9ef-76887efacc7f",\ "title": "Welcome",\ "html": "

👋 Welcome, it's great to have you here.

",\ "comment_id": "5c7ece47da174000c0c5c6d7",\ "feature_image": "https://casper.ghost.org/v2.0.0/images/welcome-to-ghost.jpg",\ "feature_image_alt": null,\ "feature_image_caption": null,\ "featured": false,\ "meta_title": null,\ "meta_description": null,\ "created_at": "2019-03-05T19:30:15.000+00:00",\ "updated_at": "2019-03-26T19:45:31.000+00:00",\ "published_at": "2012-11-27T15:30:00.000+00:00",\ "custom_excerpt": "Welcome, it's great to have you here.",\ "codeinjection_head": null,\ "codeinjection_foot": null,\ "og_image": null,\ "og_title": null,\ "og_description": null,\ "twitter_image": null,\ "twitter_title": null,\ "twitter_description": null,\ "custom_template": null,\ "canonical_url": null,\ "authors": [\ {\ "id": "5951f5fca366002ebd5dbef7",\ "name": "Ghost",\ "slug": "ghost",\ "profile_image": "https://demo.ghost.io/content/images/2017/07/ghost-icon.png",\ "cover_image": null,\ "bio": "The professional publishing platform",\ "website": "https://ghost.org",\ "location": null,\ "facebook": "ghost",\ "twitter": "@tryghost",\ "meta_title": null,\ "meta_description": null,\ "url": "https://demo.ghost.io/author/ghost/"\ }\ ],\ "tags": [\ {\ "id": "59799bbd6ebb2f00243a33db",\ "name": "Getting Started",\ "slug": "getting-started",\ "description": null,\ "feature_image": null,\ "visibility": "public",\ "meta_title": null,\ "meta_description": null,\ "url": "https://demo.ghost.io/tag/getting-started/"\ }\ ],\ "primary_author": {\ "id": "5951f5fca366002ebd5dbef7",\ "name": "Ghost",\ "slug": "ghost",\ "profile_image": "https://demo.ghost.io/content/images/2017/07/ghost-icon.png",\ "cover_image": null,\ "bio": "The professional publishing platform",\ "website": "https://ghost.org",\ "location": null,\ "facebook": "ghost",\ "twitter": "@tryghost",\ "meta_title": null,\ "meta_description": null,\ "url": "https://demo.ghost.io/author/ghost/"\ },\ "primary_tag": {\ "id": "59799bbd6ebb2f00243a33db",\ "name": "Getting Started",\ "slug": "getting-started",\ "description": null,\ "feature_image": null,\ "visibility": "public",\ "meta_title": null,\ "meta_description": null,\ "url": "https://demo.ghost.io/tag/getting-started/"\ },\ "url": "https://demo.ghost.io/welcome-short/",\ "excerpt": "Welcome, it's great to have you here."\ }\ ] } ### Pages Pages are static resources that are not included in channels or collections on the Ghost front-end. The API will only return pages that were created as resources and will not contain routes created with [dynamic routing](/docs/themes/routing/) . GET /content/pages/ GET /content/pages/{id}/ GET /content/pages/slug/{slug}/ Pages are structured identically to posts. The response object will look the same, only the resource key will be `pages`. By default, pages are ordered by title when fetching more than one. ### Tags Tags are the [primary taxonomy](/docs/publishing/#tags) within a Ghost site. GET /content/tags/ GET /content/tags/{id}/ GET /content/tags/slug/{slug}/ By default, internal tags are always included, use `filter=visibility:public` to limit the response directly or use the [tags helper](/docs/themes/helpers/tags/) to handle filtering and outputting the response. Tags that are not associated with a post are not returned. You can supply `include=count.posts` to retrieve the number of posts associated with a tag. { "tags": [\ {\ "slug": "getting-started",\ "id": "5ddc9063c35e7700383b27e0",\ "name": "Getting Started",\ "description": null,\ "feature_image": null,\ "visibility": "public",\ "meta_title": null,\ "meta_description": null,\ "og_image": null,\ "og_title": null,\ "og_description": null,\ "twitter_image": null,\ "twitter_title": null,\ "twitter_description": null,\ "codeinjection_head": null,\ "codeinjection_foot": null,\ "canonical_url": null,\ "accent_color": null,\ "url": "https://docs.ghost.io/tag/getting-started/"\ }\ ] } By default, tags are ordered by name when fetching more than one. ### Authors Authors are a subset of [users](/docs/staff/) who have published posts associated with them. GET /content/authors/ GET /content/authors/{id}/ GET /content/authors/slug/{slug}/ Authors that are not associated with a post are not returned. You can supply `include=count.posts` to retrieve the number of posts associated with an author. { "authors": [\ {\ "slug": "cameron",\ "id": "5ddc9b9510d8970038255d02",\ "name": "Cameron Almeida",\ "profile_image": "https://docs.ghost.io/content/images/2019/03/1c2f492a-a5d0-4d2d-b350-cdcdebc7e413.jpg",\ "cover_image": null,\ "bio": "Editor at large.",\ "website": "https://example.com",\ "location": "Cape Town",\ "facebook": "example",\ "twitter": "@example",\ "meta_title": null,\ "meta_description": null,\ "url": "https://docs.ghost.io/author/cameron/"\ }\ ] } ### Settings Settings contain the global settings for a site. GET /content/settings/ The settings endpoint is a special case. You will receive a single object, rather than an array. This endpoint doesn’t accept any query parameters. { "settings": { "title": "Ghost", "description": "The professional publishing platform", "logo": "https://docs.ghost.io/content/images/2014/09/Ghost-Transparent-for-DARK-BG.png", "icon": "https://docs.ghost.io/content/images/2017/07/favicon.png", "accent_color": null, "cover_image": "https://docs.ghost.io/content/images/2019/10/publication-cover.png", "facebook": "ghost", "twitter": "@tryghost", "lang": "en", "timezone": "Etc/UTC", "codeinjection_head": null, "codeinjection_foot": "", "navigation": [\ {\ "label": "Home",\ "url": "/"\ },\ {\ "label": "About",\ "url": "/about/"\ },\ {\ "label": "Getting Started",\ "url": "/tag/getting-started/"\ },\ {\ "label": "Try Ghost",\ "url": "https://ghost.org"\ }\ ], "secondary_navigation": [], "meta_title": null, "meta_description": null, "og_image": null, "og_title": null, "og_description": null, "twitter_image": null, "twitter_title": null, "twitter_description": null, "members_support_address": "noreply@docs.ghost.io", "url": "https://docs.ghost.io/" } } ### Tiers Tiers allow publishers to create multiple options for an audience to become paid subscribers. Each tier can have its own price points, benefits, and content access levels. Ghost connects tiers directly to the publication’s Stripe account. #### Usage The tiers endpoint returns a list of tiers for the site, filtered by their visibility criteria. GET /content/tiers/ Tiers are returned in order of increasing monthly price. { "tiers": [\ {\ "id": "62307cc71b4376a976734037",\ "name": "Free",\ "description": null,\ "slug": "free",\ "active": true,\ "type": "free",\ "welcome_page_url": null,\ "created_at": "2022-03-15T11:47:19.000Z",\ "updated_at": "2022-03-15T11:47:19.000Z",\ "stripe_prices": null,\ "benefits": null,\ "visibility": "public"\ },\ {\ "id": "6230d7c8c62265c44f24a594",\ "name": "Gold",\ "description": null,\ "slug": "gold",\ "active": true,\ "type": "paid",\ "welcome_page_url": "/welcome-to-gold",\ "created_at": "2022-03-15T18:15:36.000Z",\ "updated_at": "2022-03-15T18:16:00.000Z",\ "stripe_prices": null,\ "benefits": null,\ "visibility": "public"\ }\ ] } #### Working example # cURL # Real endpoint - copy and paste to see! curl "https://demo.ghost.io/ghost/api/content/tiers/?key=22444f78447824223cefc48062&include=benefits,monthly_price,yearly_price" returns: { "tiers": [\ {\ "id": "61ee7f5c5a6309002e738c41",\ "name": "Free",\ "description": null,\ "slug": "61ee7f5c5a6309002e738c41",\ "active": true,\ "type": "free",\ "welcome_page_url": "/",\ "created_at": "2022-01-24T10:28:44.000Z",\ "updated_at": null,\ "stripe_prices": null,\ "monthly_price": null,\ "yearly_price": null,\ "benefits": [],\ "visibility": "public"\ },\ {\ "id": "60815dbe9af732002f9e02fa",\ "name": "Ghost Subscription",\ "description": null,\ "slug": "ghost-subscription",\ "active": true,\ "type": "paid",\ "welcome_page_url": "/",\ "created_at": "2021-04-22T12:27:58.000Z",\ "updated_at": "2022-01-12T17:22:29.000Z",\ "stripe_prices": null,\ "monthly_price": 500,\ "yearly_price": 5000,\ "currency": "usd",\ "benefits": [],\ "visibility": "public"\ }\ ], "meta": { "pagination": { "page": 1, "limit": 15, "pages": 1, "total": 2, "next": null, "prev": null } } } * * * Parameters ---------- Query parameters provide fine-grained control over responses. All endpoints accept `include` and `fields`. Browse endpoints additionally accept `filter`, `limit`, `page` and `order`. The values provided as query parameters MUST be url encoded when used directly. The [client libraries](/docs/content-api/javascript/) will handle this for you. ### Include Tells the API to return additional data related to the resource you have requested. The following includes are available: * Posts & Pages: `authors`, `tags` * Authors: `count.posts` * Tags: `count.posts` * Tiers: `monthly_price`, `yearly_price`, `benefits` Includes can be combined with a comma, e.g., `&include=authors,tags`. For posts and pages: * `&include=authors` will add `"authors": [{...},]` and `"primary_author": {...}` * `&include=tags` will add `"tags": [{...},]` and `"primary_tag": {...}` For authors and tags: * `&include=count.posts` will add `"count": {"posts": 7}` to the response. For tiers: * `&include=monthly_price,yearly_price,benefits` will add monthly price, yearly price, and benefits data. ### Fields Limit the fields returned in the response object. Useful for optimizing queries, but does not play well with include. E.g. for posts `&fields=title,url` would return: { "posts": [\ {\ "id": "5b7ada404f87d200b5b1f9c8",\ "title": "Welcome to Ghost",\ "url": "https://demo.ghost.io/welcome/"\ }\ ] } ### Formats (Posts and Pages only) By default, only `html` is returned, however each post and page in Ghost has 2 available formats: `html` and `plaintext`. * `&formats=html,plaintext` will additionally return the plaintext format. ### Filter (Browse requests only) Apply fine-grained filters to target specific data. * `&filter=featured:true` on posts returns only those marked featured. * `&filter=tag:getting-started` on posts returns those with the tag slug that matches `getting-started`. * `&filter=visibility:public` on tiers returns only those marked as publicly visible. The possibilities are extensive! Query strings are explained in detail in the [filtering](#filtering) section. ### Limit (Browse requests only) By default, only 15 records are returned at once. * `&limit=5` would return only 5 records. * `&limit=all` will return all records - use carefully! ### Page (Browse requests only) By default, the first 15 records are returned. * `&page=2` will return the second set of 15 records. ### Order (Browse requests only) Different resources have a different default sort order: * Posts: `published_at DESC` (newest post first) * Pages: `title ASC` (alphabetically by title) * Tags: `name ASC` (alphabetically by name) * Authors: `name ASC` (alphabetically by name) * Tiers: `monthly_price ASC` (from lowest to highest monthly price) The syntax for modifying this follows SQL order by syntax: * `&order=published_at%20asc` would return posts with the newest post last * * * Filtering --------- Ghost uses a query language called NQL to allow filtering API results. You can filter any field or included field using matches, greater/less than or negation, as well as combining with and/or. NQL doesn’t yet support ’like’ or partial matches. Filter strings must be URL encoded. The [{{get}}](/docs/themes/helpers/get/) helper and [client library](/docs/content-api/javascript/) handle this for you. At it’s most simple, filtering works the same as in GMail, GitHub or Slack - you provide a field and a value, separated by a colon. ### Syntax Reference #### Filter Expressions A **filter expression** is a string which provides the **property**, **operator** and **value** in the form **property:_operator_value**: * **property** - a path representing the field to filter on * **:** - separator between **property** and an **operator**\-**value** expression * **operator** (optional) - how to compare values (`:` on its own is roughly `=`) * **value** - the value to match against #### Property Matches: `[a-zA-Z_][a-zA-Z0-9_.]` * can contain only alpha-numeric characters and `_` * cannot contain whitespace * must start with a letter * supports `.` separated paths, E.g. `authors.slug` or `posts.count` * is always lowercase, but accepts and converts uppercase #### Value Can be one of the following * **null** * **true** * **false** * a **_number_** (integer) * a **literal** * Any character string which follows these rules: * Cannot start with `-` but may contain it * Cannot contain any of these symbols: `'"+,()><=[]` unless they are escaped * Cannot contain whitespace * a **string** * `'` string here `'` Any character except a single or double quote surrounded by single quotes * Single or Double quote \_\_MUST \_\_be escaped\* * Can contain whitespace * A string can contain a date any format that can be understood by `new Date()` * a **relative date** * Uses the pattern now-30d * Must start with now * Can use - or + * Any integer can be used for the size of the interval * Supports the following intervals: d, w, M, y, h, m, s #### Operators * `-` - not * `>` - greater than * `>=` - greater than or equals * `<` - less than * `<=` - less than or equals * `~` - contains * `~^` - starts with * `~$` - ends with * `[` value, value, … `]` - “in” group, can be negated with `-` #### Combinations * `+` - represents and * `,` - represents or * `(` filter expression `)` - overrides operator precedence #### Strings vs Literals Most of the time, there’s no need to put quotes around strings when building filters in Ghost. If you filter based on slugs, slugs are always compatible with literals. However, in some cases you may need to use a string that contains one of the other characters used in the filter syntax, e.g. dates & times contain`:`. Use single-quotes for these. * * * Pagination ---------- All browse endpoints are paginated, returning 15 records by default. You can use the [page](#page) and [limit](#limit) parameters to move through the pages of records. The response object contains a `meta.pagination` key with information on the current location within the records: "meta":{ "pagination":{ "page":1, "limit":2, "pages":1, "total":1, "next":null, "prev":null } } * * * Errors ------ The Content API will generate errors for the following cases: * Status 400: Badly formed queries e.g. filter parameters that are not correctly encoded * Status 401: Authentication failures e.g. unrecognised keys * Status 404: Unknown resources e.g. data which is not public * Status 500: Server errors e.g. where something has gone Errors are also formatted in JSON, as an array of error objects. The HTTP status code of the response along with the `errorType` property indicate the type of error. The `message` field is designed to provide clarity on what exactly has gone wrong. { "errors": [\ {\ "message": "Unknown Content API Key",\ "errorType": "UnauthorizedError"\ }\ ] } * * * Versioning ---------- See [API versioning](/docs/faq/api-versioning/) for full details of the API versions and their stability levels. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Using Ghost as a headless CMS with JAMstack Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) How to use Ghost as a headless CMS with popular static site generators Ghost ships with a default front-end theme layer built with Handlebars, but based on its flexible [architecture](/docs/architecture/) it also be used as a headless CMS with third party front-end frameworks. We have setup guides for most of the most popular frameworks and how to use Ghost with them. [![Next.js](/images/docs/jamstack/nextjs-logo.svg)](/docs/jamstack/next/) [![Gatsby.js](/images/docs/jamstack/gatsby-logo.svg)](/docs/jamstack/gatsby/) [![Hexo](/images/docs/jamstack/hexo-logo.svg)](/docs/jamstack/hexo/) [![Nuxt.js](/images/docs/jamstack/nuxtjs-logo_hu9cae5061ed1f69c9d83af67ed2b31db3_33854_921x0_resize_q100_h2_box_3.webp)](/docs/jamstack/nuxt/) [![VuePress](/images/docs/jamstack/vuepress-logo_hu682ba1d96ca023ceb24ae8867fa88f58_8588_1031x0_resize_q100_h2_box_3.webp)](/docs/jamstack/vuepress/) [![Gridsome](/images/docs/jamstack/gridsome-logo.svg)](/docs/jamstack/gridsome/) [![Eleventy](/images/docs/jamstack/eleventy-logo.svg)](/docs/jamstack/eleventy/) [![Custom](/images/docs/jamstack/custom.svg)](/docs/jamstack/custom/) Tips for using Ghost headless ----------------------------- Something to keep in mind is that Ghost’s default front-end is not just a theme layer, but also contains a large subset of functionality that is commonly required by most publishers, including: * Tag archives, routes and templates * Author archives, routes and templates * Generated sitemap.xml for SEO * Intelligent output and fallbacks for SEO meta data * Automatic Open Graph structured data * Automatic support for Twitter Cards * Automatic Google AMP routes and templates * Custom routes and automatic pagination * Front-end code injection from admin When using a statically generated front-end, all of this functionality must be re-implemented. Getting a list of posts from the API is usually the easy part, while taking care of the long tail of extra features is the bulk of the work needed to make this work well. ### Memberships Ghost’s membership functionality is **not** compatible with headless setups. To use features like our Stripe integration for paid subscriptions, content gating, comments, analytics, offers, complimentary plans, trials, and more — Ghost must be used with its frontend layer. ### Working with images The Ghost API returns content HTML including image tags with absolute URLs, pointing at the origin of the Ghost install. This is intentional, because Ghost itself is designed (primarily) to be source of truth for serving optimised assets, and may also be installed in a subdirectory. When using a static front-end, you can either treat the Ghost install as a CDN origin for uploaded assets, or you can write additional logic in your front-end build to download embedded images locally, and rewrite the returned HTML to point to the local references instead. ### Disabling Ghost’s default front-end When using a headless front-end with Ghost, you’ll want to disable Ghost’s default front-end to prevent duplicate content issues where search engines would see the same content on two different domains. The easiest way to do this is to enable ‘Private Site Mode’ under `Settings > General` - which will put a password on your Ghost install’s front-end, disable all SEO features, and serve a `noindex` meta tag. You can also use dynamic redirects, locally or at a DNS level, to forward traffic automatically from the Ghost front-end to your new headless front-end - but this is a more fragile setup. If you use Ghost’s built-in newsletter functionality, unsubscribe links in emails will point to the Ghost origin - and these URLs will break if redirected. Preview URLs and other dynamically generated paths may also behave unexpectedly when blanket redirects are used. Usually ‘Private Site Mode’ is the better option. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Admin API Documentation Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) It’s possible to create and manage your content using the Ghost Admin API. Our content management interface, Ghost Admin, uses the admin API - which means that everything Ghost Admin can do is also possible with the API, and a whole lot more! Secure authentication is available either as a User with role-based permissions, or as an integration with a single standard set of permissions designed to support common publishing workflows. The API is RESTful with predictable resource URLs, standard HTTP verbs, response codes and authentication used throughout. Requests and responses are JSON-encoded with consistent patterns and inline relations and responses are customisable using powerful query parameters. API Clients ----------- ### JavaScript Client Library We’ve developed an [API client for JavaScript](/docs/admin-api/javascript/) , that simplifies authenticating with the admin API, and makes reading and writing data a breeze. The client is designed for use with integrations, supporting token authentication and the endpoints available to integrations. Structure --------- ### Base URL `https://{admin_domain}/ghost/api/admin/` All admin API requests start with this base URL. Your admin domain can be different to your main domain, and may include a subdirectory. Using the correct domain and protocol are critical to getting consistent behaviour, particularly when dealing with CORS in the browser. All Ghost(Pro) blogs have a `*.ghost.io` domain as their admin domain and require https. ### Accept-Version Header `Accept-Version: v{major}.{minor}` Use the `Accept-Version` header to indicate the minimum version of Ghost’s API to operate with. See [API Versioning](/docs/faq/api-versioning/) for more details. ### JSON Format The API uses a consistent JSON structure for all requests and responses: { "resource_type": [{\ ...\ }], "meta": {} } * `resource_type`: will always match the resource name in the URL. All resources are returned wrapped in an array, with the exception of `/site/` and `/settings/`. * `meta`: contains [pagination](/docs/content-api/#pagination) information for browse requests. #### Composing requests When composing JSON payloads to send to the API as POST or PUT requests, you must always use this same format, unless the documentation for an endpoint says otherwise. Requests with JSON payloads require the `Content-Type: application/json` header. Most request libraries have JSON-specific handling that will do this for you. ### Pagination All browse endpoints are paginated, returning 15 records by default. You can use the [page](#page) and [limit](#limit) parameters to move through the pages of records. The response object contains a `meta.pagination` key with information on the current location within the records: "meta": { "pagination": { "page": 1, "limit": 2, "pages": 1, "total": 1, "next": null, "prev": null } } ### Parameters Query parameters provide fine-grained control over responses. All endpoints accept `include` and `fields`. Browse endpoints additionally accept `filter`, `limit`, `page` and `order`. Some endpoints have their own specific parameters. The values provided as query parameters MUST be url encoded when used directly. The [client library](/docs/admin-api/javascript/) will handle this for you. ### Filtering See the [Content API](/docs/content-api/#filter) . Authentication -------------- There are three methods for authenticating with the Admin API: [token authentication](#token-authentication) , [user authentication](#user-authentication) and [staff access token authentication](#staff-access-token-authentication) . Most applications integrating with the Ghost Admin API should use token authentication. The JavaScript Admin API Client supports token authentication and staff access token authentication. ### Choosing an authentication method **Token authentication** is intended for integrations that handle common workflows, such as publishing new content, or sharing content to other platforms. Using tokens, you authenticate as an integration. Each integration can have associated API keys & webhooks and are able to perform API requests independently of users. Admin API keys are used to generate short-lived single-use JSON Web Tokens (JWTs), which are then used to authenticate a request. The API Key is secret, and therefore this authentication method is only suitable for secure server side environments. **User authentication** is intended for fully-fledged clients where different users login and manage various resources as themselves. Using an email address and password, you authenticate as a specific user, with their role-based permissions. Via the session API, credentials are swapped for a cookie-based session, which is then used to authenticate further API requests. Provided that passwords are entered securely, user-authentication is safe for use in the browser. **Staff access token authentication** is intended for clients where different users login and manage various resources as themselves, without having to share their password. Using a token found in a user’s settings page you authenticate as a specific user, with their role-based permissions. You can use this token the same way you would use an integration token. ### Permissions Integrations have a restricted set of fixed permissions allowing access to certain endpoints e.g. `GET /users/` or `POST /posts/`. The full set of endpoints that integrations can access are those listed as [endpoints](#endpoints) on this page. User permissions are dependent entirely on their role. You can find more details in the [team management guide](/help/managing-your-team/) . Authenticating as a user with the Owner or Admin role will give access to the full set of API endpoints. Many endpoints can be discovered by inspecting the requests made by Ghost Admin, the [endpoints](#endpoints) listed on this page are those stable enough to document. ### Token Authentication Token authentication is a simple, secure authentication mechanism using JSON Web Tokens (JWTs) to authenticate as an integration. Each integration is issued with an admin API key, which is used to generate a JWT token and then provided to the API via the standard HTTP Authorization header. The admin API key must be kept private, therefore token authentication is not suitable for browsers or other insecure environments, unlike the Content API key. #### Key Admin API keys can be obtained by creating a new `Custom Integration` under the Integrations screen in Ghost Admin. Keys for individual users can be found on their respective settings page. ![Get a Ghost Admin API key](/images/docs/apikey_huc23d3a1fbe859434094a9db94f574d9a_265920_2920x0_resize_q100_h2_box_3.webp) Admin API keys are made up of an id and secret, separated by a colon. These values are used separately to get a signed JWT token, which is used in the Authorization header of the request: curl -H "Authorization: Ghost $token" -H "Accept-Version: $version" https://{admin_domain}/ghost/api/admin/{resource}/ The Admin API JavaScript client handles all the technical details of generating a JWT from an admin API key, meaning you only have to provide your url, version and key to start making requests. #### Token Generation If you’re using a language other than JavaScript, or are not using our client library, you’ll need to generate the tokens yourself. It is not safe to swap keys for tokens in the browser, or in any other insecure environment. There are a myriad of [libraries](https://jwt.io/#libraries) available for generating JWTs in different environments. JSON Web Tokens are made up of a header, a payload and a secret. The values needed for the header and payload are: // Header { "alg": "HS256", "kid": {id}, // ID from your API key "typ": "JWT" } // Payload { // Timestamps are seconds sine the unix epoch, not milliseconds "exp": {timestamp}, // Max 5 minutes after 'now' "iat": {timestamp}, // 'now' (max 5 minutes after 'exp') "aud": "/admin/" } The libraries on [https://jwt.io](https://jwt.io) all work slightly differently, but all of them allow you to specify the above required values, including setting the signing algorithm to the required HS-256. Where possible, the API will provide specific error messages when required values are missing or incorrect. Regardless of language, you’ll need to: 1. Split the API key by the `:` into an `id` and a `secret` 2. Decode the hexadecimal secret into the original binary byte array 3. Pass these values to your JWT library of choice, ensuring that the header and payload are correct. #### Token Generation Examples These examples show how to generate a valid JWT in various languages & JWT libraries. The bash example shows step-by-step how to create a token without using a library. Bash (cURL) | | | | --- | --- | | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 | #!/usr/bin/env bash

# Admin API key goes here
KEY="YOUR_ADMIN_API_KEY"

# Split the key into ID and SECRET
TMPIFS=$IFS
IFS=':' read ID SECRET <<< "$KEY"
IFS=$TMPIFS

# Prepare header and payload
NOW=$(date +'%s')
FIVE_MINS=$(($NOW + 300))
HEADER="{\"alg\": \"HS256\",\"typ\": \"JWT\", \"kid\": \"$ID\"}"
PAYLOAD="{\"iat\":$NOW,\"exp\":$FIVE_MINS,\"aud\": \"/admin/\"}"

# Helper function for performing base64 URL encoding
base64_url_encode() {
declare input=${1:-$( # Use `tr` to URL encode the output from base64.
printf '%s' "${input}" \| base64 \| tr -d '=' \| tr '+' '-' \| tr '/' '_'
}

# Prepare the token body
header_base64=$(base64_url_encode "$HEADER")
payload_base64=$(base64_url_encode "$PAYLOAD")

header_payload="${header_base64}.${payload_base64}"

# Create the signature
signature=$(printf '%s' "${header_payload}" \| openssl dgst -binary -sha256 -mac HMAC -macopt hexkey:$SECRET \| base64_url_encode)

# Concat payload and signature into a valid JWT token

TOKEN="${header_payload}.${signature}"

# Make an authenticated request to create a post
curl -H "Authorization: Ghost $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept-Version: v3.0" \
-d '{"posts":[{"title":"Hello world"}]}' \
"http://localhost:2368/ghost/api/admin/posts/" | JavaScript (Client) | | | | --- | --- | | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | // The admin API client is the easiest way to use the API
const GhostAdminAPI = require('@tryghost/admin-api');

// Configure the client
const api = new GhostAdminAPI({
url: 'http://localhost:2368/',
// Admin API key goes here
key: 'YOUR_ADMIN_API_KEY',
version: 'v3'
});

// Make an authenticated request
api.posts.add({title: 'Hello world'})
.then(response => console.log(response))
.catch(error => console.error(error)); | JavaScript | | | | --- | --- | | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | // Create a token without the client
const jwt = require('jsonwebtoken');
const axios = require('axios');

// Admin API key goes here
const key = 'YOUR_ADMIN_API_KEY';

// Split the key into ID and SECRET
const [id, secret] = key.split(':');

// Create the token (including decoding secret)
const token = jwt.sign({}, Buffer.from(secret, 'hex'), {
keyid: id,
algorithm: 'HS256',
expiresIn: '5m',
audience: `/admin/`
});

// Make an authenticated request to create a post
const url = 'http://localhost:2368/ghost/api/admin/posts/';
const headers = { Authorization: `Ghost ${token}` };
const payload = { posts: [{ title: 'Hello World' }] };
axios.post(url, payload, { headers })
.then(response => console.log(response))
.catch(error => console.error(error)); | Ruby | | | | --- | --- | | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | require 'httparty'
require 'jwt'

# Admin API key goes here
key = 'YOUR_ADMIN_API_KEY'

# Split the key into ID and SECRET
id, secret = key.split(':')

# Prepare header and payload
iat = Time.now.to_i

header = {alg: 'HS256', typ: 'JWT', kid: id}
payload = {
iat: iat,
exp: iat + 5 * 60,
aud: '/admin/'
}

# Create the token (including decoding secret)
token = JWT.encode payload, [secret].pack('H*'), 'HS256', header

# Make an authenticated request to create a post
url = 'http://localhost:2368/ghost/api/admin/posts/'
headers = {Authorization: "Ghost #{token}", 'Accept-Version': "v4.0"}
body = {posts: [{title: 'Hello World'}]}
puts HTTParty.post(url, body: body, headers: headers) | Python | | | | --- | --- | | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | import requests # pip install requests
import jwt # pip install pyjwt
from datetime import datetime as date

# Admin API key goes here
key = 'YOUR_ADMIN_API_KEY'

# Split the key into ID and SECRET
id, secret = key.split(':')

# Prepare header and payload
iat = int(date.now().timestamp())

header = {'alg': 'HS256', 'typ': 'JWT', 'kid': id}
payload = {
'iat': iat,
'exp': iat + 5 * 60,
'aud': '/admin/'
}

# Create the token (including decoding secret)
token = jwt.encode(payload, bytes.fromhex(secret), algorithm='HS256', headers=header)

# Make an authenticated request to create a post
url = 'http://localhost:2368/ghost/api/admin/posts/'
headers = {'Authorization': 'Ghost {}'.format(token)}
body = {'posts': [{'title': 'Hello World'}]}
r = requests.post(url, json=body, headers=headers)

print(r) | ### User Authentication User Authentication is an advanced, session-based authentication method that provides access to all API endpoints and actions according to the role of the user being authenticated. Authenticating as a user requires an application to collect a user’s email and password, and swap the credentials for a cookie. The cookie is then used to maintain a session. #### Creating a Session The session and authentication endpoints have custom payloads, different to the standard JSON resource format. POST /admin/session/ ##### Request To create a new session, send a username and password to the sessions endpoint, in this format: // POST /admin/session/ { "username": "{email address}", "password": "{password}" } This request should also have an Origin header. See [CSRF protection](#csrf-protection) for details. ###### Response `201 Created`: A successful session creation will return HTTP `201` response with an empty body and a `set-cookie` header, in the following format: set-cookie: ghost-admin-api-session={session token}; Path=/ghost; Expires=Mon, 26 Aug 2019 19:14:07 GMT; HttpOnly; SameSite=Lax #### Making authenticated API requests The provided session cookie should be provided with every subsequent API request: * When making the request from a browser using the `fetch` API, pass `credentials: 'include'` to ensure cookies are sent. * When using XHR you should set the `withCredentials` property of the xhr to `true` * When using cURL you can use the `--cookie` and `--cookie-jar` options to store and send cookies from a text file. ##### CSRF Protection Session-based requests must also include either an Origin (preferred) or a Referer header. The value of these headers is checked against the original session creation requests, in order to prevent Cross-Site Request Forgery (CSRF) in a browser environment. In a browser environment, these headers are handled automatically. For server-side or native apps, the Origin header should be sent with an identifying URL as the value. #### Session-based Examples # cURL # Create a session, and store the cookie in ghost-cookie.txt curl -c ghost-cookie.txt -d username=me@site.com -d password=secretpassword \ -H "Origin: https://myappsite.com" \ -H "Accept-Version: v3.0" \ https://demo.ghost.io/ghost/api/admin/session/ # Use the session cookie to create a post curl -b ghost-cookie.txt \ -d '{"posts": [{"title": "Hello World"}]}' \ -H "Content-Type: application/json" \ -H "Accept-Version: v3.0" \ -H "Origin: https://myappsite.com" \ https://demo.ghost.io/ghost/api/admin/posts/ ### Staff access token authentication Staff access token authentication is a simple, secure authentication mechanism using JSON Web Tokens (JWTs) to authenticate as a user. Each user can create and refresh their own token, which is used to generate a JWT token and then provided to the API via the standard HTTP Authorization header. For more information on usage, please refer to the [token authentication section](#token-authentication) . The staff access token must be kept private, therefore staff access token authentication is not suitable for browsers or other insecure environments. Endpoints --------- These are the endpoints & methods currently available to integrations. More endpoints are available through user authentication. Each endpoint has a stability index, see [versioning](/docs/faq/api-versioning) for more information. | Resource | Methods | Stability | | --- | --- | --- | | [/posts/](/docs/admin-api/#posts) | Browse, Read, Edit, Add, Copy, Delete | Stable | | [/pages/](/docs/admin-api/#pages) | Browse, Read, Edit, Add, Copy, Delete | Stable | | /tags/ | Browse, Read, Edit, Add, Delete | Experimental | | [/tiers/](/docs/admin-api/#tiers) | Browse, Read, Edit, Add | Stable | | [/offers/](/docs/admin-api/#offers) | Browse, Read, Edit, Add | Stable | | [/members/](/docs/admin-api/#members) | Browse, Read, Edit, Add | Stable | | [/users/](/docs/admin-api/#users) | Browse, Read | Stable | | [/images/](/docs/admin-api/#images) | Upload | Stable | | [/themes/](/docs/admin-api/#themes) | Upload, Activate | Stable | | [/site/](/docs/admin-api/#site) | Read | Stable | | [/webhooks/](/docs/admin-api/#webhooks) | Edit, Add, Delete | Stable | Posts ----- Posts are the [primary resource](/docs/posts/) in a Ghost site, providing means for publishing, managing and displaying content. At the heart of every post is a Lexical field, containing a standardised JSON-based representation of your content, which can be rendered in multiple formats. GET /admin/posts/ GET /admin/posts/{id}/ GET /admin/posts/slug/{slug}/ POST /admin/posts/ PUT /admin/posts/{id}/ DELETE /admin/posts/{id}/ ### The post object Whenever you fetch, create, or edit a post, the API will respond with an array of one or more post objects. These objects will include all related tags, authors, and author roles. By default, the API expects and returns content in the **Lexical** format only. To include **HTML** in the response use the `formats` parameter: // GET /admin/posts/?formats=html,lexical { "posts": [\ {\ "slug": "welcome-short",\ "id": "5ddc9141c35e7700383b2937",\ "uuid": "a5aa9bd8-ea31-415c-b452-3040dae1e730",\ "title": "Welcome",\ "lexical": "{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello, beautiful world! 👋\",\"type\":\"extended-text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}",\ "html": "

Hello, beautiful world! 👋

",\ "comment_id": "5ddc9141c35e7700383b2937",\ "feature_image": "https://static.ghost.org/v3.0.0/images/welcome-to-ghost.png",\ "feature_image_alt": null,\ "feature_image_caption": null,\ "featured": false,\ "status": "published",\ "visibility": "public",\ "created_at": "2019-11-26T02:43:13.000Z",\ "updated_at": "2019-11-26T02:44:17.000Z",\ "published_at": "2019-11-26T02:44:17.000Z",\ "custom_excerpt": null,\ "codeinjection_head": null,\ "codeinjection_foot": null,\ "custom_template": null,\ "canonical_url": null,\ "tags": [\ {\ "created_at": "2019-11-26T02:39:31.000Z",\ "description": null,\ "feature_image": null,\ "id": "5ddc9063c35e7700383b27e0",\ "meta_description": null,\ "meta_title": null,\ "name": "Getting Started",\ "slug": "getting-started",\ "updated_at": "2019-11-26T02:39:31.000Z",\ "url": "https://docs.ghost.io/tag/getting-started/",\ "visibility": "public"\ }\ ],\ "authors": [\ {\ "id": "5951f5fca366002ebd5dbef7",\ "name": "Ghost",\ "slug": "ghost-user",\ "email": "info@ghost.org",\ "profile_image": "//www.gravatar.com/avatar/2fab21a4c4ed88e76add10650c73bae1?s=250&d=mm&r=x",\ "cover_image": null,\ "bio": null,\ "website": "https://ghost.org",\ "location": "The Internet",\ "facebook": "ghost",\ "twitter": "@ghost",\ "accessibility": null,\ "status": "locked",\ "meta_title": null,\ "meta_description": null,\ "tour": null,\ "last_seen": null,\ "created_at": "2019-11-26T02:39:32.000Z",\ "updated_at": "2019-11-26T04:30:57.000Z",\ "roles": [\ {\ "id": "5ddc9063c35e7700383b27e3",\ "name": "Author",\ "description": "Authors",\ "created_at": "2019-11-26T02:39:31.000Z",\ "updated_at": "2019-11-26T02:39:31.000Z"\ }\ ],\ "url": "https://docs.ghost.io/author/ghost-user/"\ }\ ],\ "primary_author": {\ "id": "5951f5fca366002ebd5dbef7",\ "name": "Ghost",\ "slug": "ghost-user",\ "email": "info@ghost.org",\ "profile_image": "//www.gravatar.com/avatar/2fab21a4c4ed88e76add10650c73bae1?s=250&d=mm&r=x",\ "cover_image": null,\ "bio": null,\ "website": "https://ghost.org",\ "location": "The Internet",\ "facebook": "ghost",\ "twitter": "@ghost",\ "accessibility": null,\ "status": "locked",\ "meta_title": null,\ "meta_description": null,\ "tour": null,\ "last_seen": null,\ "created_at": "2019-11-26T02:39:32.000Z",\ "updated_at": "2019-11-26T04:30:57.000Z",\ "roles": [\ {\ "id": "5ddc9063c35e7700383b27e3",\ "name": "Author",\ "description": "Authors",\ "created_at": "2019-11-26T02:39:31.000Z",\ "updated_at": "2019-11-26T02:39:31.000Z"\ }\ ],\ "url": "https://docs.ghost.io/author/ghost-user/"\ },\ "primary_tag": {\ "id": "5ddc9063c35e7700383b27e0",\ "name": "Getting Started",\ "slug": "getting-started",\ "description": null,\ "feature_image": null,\ "visibility": "public",\ "meta_title": null,\ "meta_description": null,\ "created_at": "2019-11-26T02:39:31.000Z",\ "updated_at": "2019-11-26T02:39:31.000Z",\ "og_image": null,\ "og_title": null,\ "og_description": null,\ "twitter_image": null,\ "twitter_title": null,\ "twitter_description": null,\ "codeinjection_head": null,\ "codeinjection_foot": null,\ "canonical_url": null,\ "accent_color": null,\ "parent": null,\ "url": "https://docs.ghost.io/tag/getting-started/"\ },\ "url": "https://docs.ghost.io/welcome-short/",\ "excerpt": "👋 Welcome, it's great to have you here.",\ "og_image": null,\ "og_title": null,\ "og_description": null,\ "twitter_image": null,\ "twitter_title": null,\ "twitter_description": null,\ "meta_title": null,\ "meta_description": null,\ "email_only": false,\ "newsletter": {\ "id": "62750bff2b868a34f814af08",\ "name": "Weekly newsletter",\ "description": null,\ "slug": "default-newsletter",\ "sender_name": "Weekly newsletter",\ "sender_email": null,\ "sender_reply_to": "newsletter",\ "status": "active",\ "visibility": "members",\ "subscribe_on_signup": true,\ "sort_order": 0,\ "header_image": null,\ "show_header_icon": true,\ "show_header_title": true,\ "title_font_category": "sans_serif",\ "title_alignment": "center",\ "show_feature_image": true,\ "body_font_category": "sans_serif",\ "footer_content": null,\ "show_badge": true,\ "created_at": "2022-06-06T11:52:31.000Z",\ "updated_at": "2022-06-20T07:43:43.000Z",\ "show_header_name": true,\ "uuid": "59fbce16-c0bf-4583-9bb3-5cd52db43159"\ },\ "email": {\ "id": "628f3b462de0a130909d4a6a",\ "uuid": "955305de-d89e-4468-927f-2d2b8fec88e5",\ "status": "submitted",\ "recipient_filter": "status:-free",\ "error": null,\ "error_data": "[]",\ "email_count": 256,\ "delivered_count": 256,\ "opened_count": 59,\ "failed_count": 0,\ "subject": "Welcome",\ "from": "\"Weekly newsletter\"",\ "reply_to": "noreply@example.com",\ "html": "...",\ "plaintext": "...",\ "track_opens": true,\ "submitted_at": "2022-05-26T08:33:10.000Z",\ "created_at": "2022-06-26T08:33:10.000Z",\ "updated_at": "2022-06-26T08:33:16.000Z"\ }\ }\ ] } #### Parameters When retrieving posts from the admin API, it is possible to use the `include`, `formats`, `filter`, `limit`, `page` and `order` parameters as documented for the [Content API](/docs/content-api/#parameters) . Some defaults are different between the two APIs, however the behaviour and availability of the parameters remains the same. ### Creating a Post POST /admin/posts/ Required fields: `title` Create draft and published posts with the add posts endpoint. All fields except `title` can be empty or have a default that is applied automatically. Below is a minimal example for creating a published post with content: // POST /admin/posts/ { "posts": [\ {\ "title": "My test post",\ "lexical": "{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello, beautiful world! 👋\",\"type\":\"extended-text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}",\ "status": "published"\ }\ ] } A post must always have [at least one author](#tags-and-authors) , and this will default to the staff user with the owner role when [token authentication](#token-authentication) is used. #### Source HTML The post creation endpoint is also able to convert HTML into Lexical. The conversion generates the best available Lexical representation, meaning this operation is lossy and the HTML rendered by Ghost may be different from the source HTML. For the best results ensure your HTML is well-formed, e.g. uses block and inline elements correctly. To use HTML as the source for your content instead of Lexical, use the `source` parameter: // POST /admin/posts/?source=html { "posts": [\ {\ "title": "My test post",\ "html": "

My post content. Work in progress...

",\ "status": "published"\ }\ ] } For lossless HTML conversion, you can wrap your HTML in a single Lexical card:

HTML goes here

#### Tags and Authors You can link tags and authors to any post you create in the same request body, using either short or long form to identify linked resources. Short form uses a single string to identify a tag or author resource. Tags are identified by name and authors are identified by email address: // POST /admin/posts/ { "posts": [\ {\ "title": "My test post",\ "tags": ["Getting Started", "Tag Example"],\ "authors": ["example@ghost.org", "test@ghost.org"],\ "lexical": "{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello, beautiful world! 👋\",\"type\":\"extended-text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}",\ "status": "published"\ }\ ] } Long form requires an object with at least one identifying key-value pair: // POST /admin/posts/ { "posts": [\ {\ "title": "My test post",\ "tags": [\ { "name": "my tag", "description": "a very useful tag" },\ { "name": "#hidden" }\ ],\ "authors": [\ { "id": "5c739b7c8a59a6c8ddc164a1" },\ { "id": "5c739b7c8a59a6c8ddc162c5" },\ { "id": "5c739b7c8a59a6c8ddc167d9" }\ ]\ }\ ] } Tags that cannot be matched are automatically created. If no author can be matched, Ghost will fallback to using the staff user with the owner role. ### Copying a Post POST /admin/posts/{id}/copy Required fields: `id` Duplicates an existing post, appending “Copy” to the title and slug. The API returns the duplicated post, which is created as a draft: { "posts": [\ {\ "id": "6464d266ada9197e4d34db76",\ "title": "My test post (Copy)",\ "slug": "my-test-post-copy"\ ...\ }\ ] } ### Updating a Post PUT /admin/posts/{id}/ Required fields: `updated_at` All writable fields of a post can be updated via the edit endpoint. The `updated_at` field is required as it is used to handle collision detection and ensure you’re not overwriting more recent updates. It is recommended to perform a GET request to fetch the latest data before updating a post. Below is a minimal example for updating the title of a post: // PUT admin/posts/5b7ada404f87d200b5b1f9c8/ { "posts": [\ {\ "title": "My new title",\ "updated_at": "2022-06-05T20:52:37.000Z"\ }\ ] } #### Tags and Authors Tag and author relations will be replaced, not merged. Again, the recommendation is to always fetch the latest version of a post, make any amends to this such as adding another tag to the tags array, and then send the amended data via the edit endpoint. ### Publishing and scheduling posts Posts can be published or scheduled by updating the `status`. #### Publishing a Post Publish a draft post by updating its status to `published`: // PUT admin/posts/5b7ada404f87d200b5b1f9c8/ { "posts": [\ {\ "updated_at": "2022-06-05T20:52:37.000Z",\ "status": "published"\ }\ ] } #### Scheduling a Post A post can be scheduled by updating or setting the `status` to `scheduled` and setting `published_at` to a datetime in the future: // PUT admin/posts/5b7ada404f87d200b5b1f9c8/ { "posts": [\ {\ "updated_at": "2022-06-05T20:52:37.000Z",\ "status": "scheduled",\ "published_at": "2023-06-10T11:00:00.000Z"\ }\ ] } At the time specified in `published_at`, the post will be published, email newsletters will be sent (if applicable), and the status of the post will change to `published`. For email-only posts, the status will change to `sent`. #### Sending a Post via email To send a post by email, the `newsletter` query parameter must be passed when publishing or scheduling the post, containing the newsletter’s `slug`. Optionally, a filter can be provided to send the email to a subset of members subscribed to the newsletter by passing the `email_segment` query parameter containing a valid NQL filter for members. Commonly used values are `status:free` (all free members), `status:-free` (all paid members) and `all`. If `email_segment` is not specified, the default is `all` (no additional filtering applied). Posts are sent by email if and only if an active newsletter is provided. // PUT admin/posts/5b7ada404f87d200b5b1f9c8/?newsletter=weekly-newsletter&email_segment=status%3Afree { "posts": [\ {\ "updated_at": "2022-06-05T20:52:37.000Z",\ "status": "published"\ }\ ] } When a post has been sent by email, the post object will contain the related `newsletter` and `email` objects. If the related email object has a `status` of `failed`, sending can be retried by reverting the post’s status to `draft` and then republishing the post. { "posts": [\ {\ "id": "5ddc9141c35e7700383b2937",\ ...\ "email": {\ "id": "628f3b462de0a130909d4a6a",\ "uuid": "955305de-d89e-4468-927f-2d2b8fec88e5",\ "status": "failed",\ "recipient_filter": "all",\ "error": "Email service is currently unavailable - please try again",\ "error_data": "[{...}]",\ "email_count": 2,\ "delivered_count": 0,\ "opened_count": 0,\ "failed_count": 0,\ "subject": "Welcome",\ "from": "\"Weekly newsletter\"",\ "reply_to": "noreply@example.com",\ "html": "...",\ "plaintext": "...",\ "track_opens": true,\ "submitted_at": "2022-05-26T08:33:10.000Z",\ "created_at": "2022-06-26T08:33:10.000Z",\ "updated_at": "2022-06-26T08:33:16.000Z"\ },\ ...\ }\ ] } #### Email only posts To send a post as an email without publishing it on the site, the `email_only` property must be set to `true` when publishing or scheduling the post in combination with the `newsletter` parameter: // PUT admin/posts/5b7ada404f87d200b5b1f9c8/?newsletter=weekly-newsletter { "posts": [\ {\ "updated_at": "2022-06-05T20:52:37.000Z",\ "status": "published",\ "email_only": true\ }\ ] } When an email-only post has been sent, the post will have a `status` of `sent`. ### Deleting a Post DELETE /admin/posts/{id}/ Delete requests have no payload in the request or response. Successful deletes will return an empty 204 response. Pages ----- Pages are [static resources](/docs/pages/) that are not included in channels or collections on the Ghost front-end. They are identical to posts in terms of request and response structure when working with the APIs. GET /admin/pages/ GET /admin/pages/{id}/ GET /admin/pages/slug/{slug}/ POST /admin/pages/ POST /admin/pages/{id}/copy PUT /admin/pages/{id}/ DELETE /admin/pages/{id}/ Tiers ----- Tiers allow publishers to create multiple options for an audience to become paid subscribers. Each tier can have its own price points, benefits, and content access levels. Ghost connects tiers directly to the publication’s Stripe account. ### The tier object Whenever you fetch, create, or edit a tier, the API responds with an array of one or more tier objects. By default, the API doesn’t return monthly/yearly prices or benefits. To include them in the response, use the `include` parameter with any or all of the following values: `monthly_price`, `yearly_price`, `benefits`. // GET admin/tiers/?include=monthly_price,yearly_price,benefits { "tiers": [\ {\ "id": "622727ad96a190e914ab6664",\ "name": "Free",\ "description": null,\ "slug": "free",\ "active": true,\ "type": "free",\ "welcome_page_url": null,\ "created_at": "2022-03-08T09:53:49.000Z",\ "updated_at": "2022-03-08T10:43:15.000Z",\ "stripe_prices": null,\ "monthly_price": null,\ "yearly_price": null,\ "benefits": [],\ "visibility": "public"\ },\ {\ "id": "622727ad96a190e914ab6665",\ "name": "Bronze",\ "description": "Access to basic features",\ "slug": "default-product",\ "active": true,\ "type": "paid",\ "welcome_page_url": null,\ "created_at": "2022-03-08T09:53:49.000Z",\ "updated_at": "2022-03-14T19:22:46.000Z",\ "stripe_prices": null,\ "monthly_price": 500,\ "yearly_price": 5000,\ "currency": "usd",\ "benefits": [\ "Free daily newsletter",\ "3 posts a week"\ ],\ "visibility": "public"\ }\ ], "meta": { "pagination": { "page": 1, "limit": 15, "pages": 1, "total": 2, "next": null, "prev": null } } } ### Parameters When retrieving tiers from the Admin API, it’s possible to use the `include` and `filter` parameters. Available **include** values: * `monthly_price` - include monthly price data * `yearly_price` - include yearly price data * `benefits` - include benefits data Available **filter** values: * `type:free|paid` - for filtering paid or free tiers * `visibility:public|none` - for filtering tiers based on their visibility * `active:true|false` - for filtering active or archived tiers For browse requests, it’s also possible to use `limit`, `page`, and `order` parameters as documented in the [Content API](https://ghost.org/docs/content-api/#parameters) . By default, tiers are ordered by ascending monthly price amounts. ### Creating a Tier POST /admin/tiers/ Required fields: `name` Create public and hidden tiers by using this endpoint. New tiers are always set as `active` when created. The example below creates a paid Tier with all properties including custom monthly/yearly prices, description, benefits, and welcome page. // POST /admin/tiers/ { "tiers": [\ {\ "name": "Platinum",\ "description": "Access to everything",\ "welcome_page_url": "/welcome-to-platinum",\ "visibility": "public",\ "monthly_price": 1000,\ "yearly_price": 10000,\ "currency": "usd",\ "benefits": [\ "Benefit 1",\ "Benefit 2"\ ]\ }\ ] } ### Updating a Tier PUT /admin/tiers/{id}/ Required fields: `name` Update all writable fields of a tier by using the edit endpoint. For example, rename a tier or set it as archived with this endpoint. Below is an example for updating the name and description of a tier: // PUT /admin/tiers/{id}/ { "tiers": [\ {\ "name": "Silver",\ "description": "silver"\ }\ ] } Newsletters ----------- Newsletters allow finer control over distribution of site content via email, allowing members to opt-in or opt-out of different categories of content. By default each site has one newsletter. ### The newsletter object // GET admin/newsletters/?limit=all { "newsletters": [\ {\ "id": "62750bff2b868a34f814af08",\ "name": "My Ghost site",\ "description": null,\ "slug": "default-newsletter",\ "sender_name": null,\ "sender_email": null,\ "sender_reply_to": "newsletter",\ "status": "active",\ "visibility": "members",\ "subscribe_on_signup": true,\ "sort_order": 0,\ "header_image": null,\ "show_header_icon": true,\ "show_header_title": true,\ "title_font_category": "sans_serif",\ "title_alignment": "center",\ "show_feature_image": true,\ "body_font_category": "sans_serif",\ "footer_content": null,\ "show_badge": true,\ "created_at": "2022-05-06T11:52:31.000Z",\ "updated_at": "2022-05-20T07:43:43.000Z",\ "show_header_name": true,\ "uuid": "59fbce16-c0bf-4583-9bb3-5cd52db43159"\ }\ ], "meta": { "pagination": { "page": 1, "limit": "all", "pages": 1, "total": 1, "next": null, "prev": null } } } | Key | Description | | --- | --- | | **name** | Public name for the newsletter | | **description** | (nullable) Public description of the newsletter | | **status** | `active` or `archived` - denotes if the newsletter is active or archived | | **slug** | The reference to this newsletter that can be used in the `newsletter` option when sending a post via email | | **sender\_name** | (nullable) The sender name of the emails | | **sender\_email** | (nullable) The email from which to send emails. Requires validation. | | **sender\_reply\_to** | The reply-to email address for sent emails. Can be either `newsletter` (= use `sender_email`) or `support` (use support email from Portal settings). | | **subscribe\_on\_signup** | `true`/`false`. Whether members should automatically subscribe to this newsletter on signup | | **header\_image** | (nullable) Path to an image to show at the top of emails. Recommended size 1200x600 | | **show\_header\_icon** | `true`/`false`. Show the site icon in emails | | **show\_header\_title** | `true`/`false`. Show the site name in emails | | **show\_header\_name** | `true`/`false`. Show the newsletter name in emails | | **title\_font\_category** | Title font style. Either `serif` or `sans_serif` | | **show\_feature\_image** | `true`/`false`. Show the post's feature image in emails | | **body\_font\_category** | Body font style. Either `serif` or `sans_serif` | | **footer\_content** | (nullable) Extra information or legal text to show in the footer of emails. Should contain valid HTML. | | **show\_badge** | `true`/`false`. Show you’re a part of the indie publishing movement by adding a small Ghost badge in the footer | ### Creating a Newsletter POST /admin/newsletters/ Required fields: `name` Options: `opt_in_existing` When `opt_in_existing` is set to `true`, existing members with a subscription to one or more active newsletters are also subscribed to this newsletter. The response metadata will include the number of members opted-in. Below is an example for creating a newsletter with all available properties: // POST /admin/newsletters/?opt_in_existing=true { "newsletters": [\ {\ "name": "My newly created newsletter",\ "description": "This is a newsletter description",\ "sender_reply_to": "newsletter",\ "status": "active",\ "subscribe_on_signup": true,\ "show_header_icon": true,\ "show_header_title": true,\ "show_header_name": true,\ "title_font_category": "sans_serif",\ "title_alignment": "center",\ "show_feature_image": true,\ "body_font_category": "sans_serif",\ "show_badge": true\ }\ ] } ### Updating a Newsletter PUT /admin/newsletters/629711f95d57e7229f16181c/ { "newsletters": [\ {\ "id": "62750bff2b868a34f814af08",\ "name": "My newly created newsletter",\ "description": "This is an edited newsletter description",\ "sender_name": "Daily Newsletter",\ "sender_email": null,\ "sender_reply_to": "newsletter",\ "status": "active",\ "subscribe_on_signup": true,\ "sort_order": 1,\ "header_image": null,\ "show_header_icon": true,\ "show_header_title": true,\ "title_font_category": "sans_serif",\ "title_alignment": "center",\ "show_feature_image": true,\ "body_font_category": "sans_serif",\ "footer_content": null,\ "show_badge": true,\ "show_header_name": true\ }\ ] } ### Sender email validation When updating the `sender_email` field, email verification is required before emails are sent from the new address. After updating the property, the `sent_email_verification` metadata property will be set, containing `sender_email`. The `sender_email` property will remain unchanged until the address has been verified by clicking the link that is sent to the address specified in `sender_email`. PUT /admin/newsletters/62750bff2b868a34f814af08/ { "newsletters": [\ {\ "sender_email": "daily-newsletter@domain.com"\ }\ ] } // Response { "newsletters": [\ {\ "id": "62750bff2b868a34f814af08",\ "name": "My newly created newsletter",\ "description": "This is an edited newsletter description",\ "sender_name": "Daily Newsletter",\ "sender_email": null,\ "sender_reply_to": "newsletter",\ "status": "active",\ "subscribe_on_signup": true,\ "sort_order": 1,\ "header_image": null,\ "show_header_icon": true,\ "show_header_title": true,\ "title_font_category": "sans_serif",\ "title_alignment": "center",\ "show_feature_image": true,\ "body_font_category": "sans_serif",\ "footer_content": null,\ "show_badge": true,\ "show_header_name": true\ }\ ], "meta": { "sent_email_verification": [\ "sender_email"\ ] } } Offers ------ Use offers to create a discount or special price for members signing up on a tier. ### The offer object When you fetch, create, or edit an offer, the API responds with an array of one or more offer objects. These objects include related `tier` data. // GET /admin/offers/ { "offers": [\ {\ "id": "6230dd69e8bc4d3097caefd3",\ "name": "Black friday",\ "code": "black-friday",\ "display_title": "Black friday sale!",\ "display_description": "10% off our yearly price",\ "type": "percent",\ "cadence": "year",\ "amount": 10,\ "duration": "once",\ "duration_in_months": null,\ "currency_restriction": false,\ "currency": null,\ "status": "active",\ "redemption_count": 0,\ "tier": {\ "id": "62307cc71b4376a976734038",\ "name": "Platinum"\ }\ }\ ] } | Key | Description | | --- | --- | | **display\_title** | Name displayed in the offer window | | **display\_description** | Text displayed in the offer window | | **name** | Internal name for an offer, must be unique | | **code** | Shortcode for the offer, for example: https://yoursite.com/black-friday | | **status** | `active` or `archived` - denotes if the offer is active or archived | | **type** | `percent` or `fixed` - whether the amount off is a percentage or fixed | | **amount** | Offer discount amount, as a percentage or fixed value as set in `type`. _Amount is always denoted by the smallest currency unit (e.g., 100 cents instead of $1.00 in USD)_ | | **currency** | `fixed` type offers only - specifies tier's currency as three letter ISO currency code | | **currency\_restriction** | Denotes whether the offer \`currency\` is restricted. If so, changing the currency invalidates the offer | | **duration** | `once`/`forever`/`repeating`. `repeating` duration is only available when `cadence` is `month` | | **duration\_in\_months** | Number of months offer should be repeated when `duration` is `repeating` | | **redemption\_count** | Number of times the offer has been redeemed | | **tier** | Tier on which offer is applied | | **cadence** | `month` or `year` - denotes if offer applies to tier's monthly or yearly price | ### Creating an Offer POST /admin/offers/ Required fields: `name`, `code`, `cadence`, `duration`, `amount`, `tier.id` , `type` When offer `type` is `fixed`, `currency` is also required and must match the tier’s currency. New offers are created as active by default. Below is an example for creating an offer with all properties including prices, description, and benefits: // POST /admin/offers/ { "offers": [\ {\ "name": "Black Friday",\ "code": "black-friday",\ "display_title": "Black Friday Sale!",\ "display_description": "10% off on yearly plan",\ "type": "percent",\ "cadence": "year",\ "amount": 12,\ "duration": "once",\ "duration_in_months": null,\ "currency_restriction": false,\ "currency": null,\ "status": "active",\ "redemption_count": 0,\ "tier": {\ "id": "62307cc71b4376a976734038",\ "name": "Gold"\ }\ }\ ] } ### Updating an Offer For existing offers, only `name` , `code`, `display_title` and `display_description` are editable. The example below updates `display title` and `code`. // PUT /admin/offers/{id}/ { "offers": [\ {\ "display_title": "Black Friday 2022",\ "code": "black-friday-2022"\ }\ ] } Members ------- The members resource provides an endpoint for fetching, creating, and updating member data. Fetch members (by default, the 15 newest members are returned): // GET /admin/members/?include=newsletters%2Clabels { "members": [\ {\ "id": "623199bfe8bc4d3097caefe0",\ "uuid": "4fa3e4df-85d5-44bd-b0bf-d504bbe22060",\ "email": "jamie@example.com",\ "name": "Jamie",\ "note": null,\ "geolocation": null,\ "created_at": "2022-03-16T08:03:11.000Z",\ "updated_at": "2022-03-16T08:03:40.000Z",\ "labels": [\ {\ "id": "623199dce8bc4d3097caefe9",\ "name": "Label 1",\ "slug": "label-1",\ "created_at": "2022-03-16T08:03:40.000Z",\ "updated_at": "2022-03-16T08:03:40.000Z"\ }\ ],\ "subscriptions": [],\ "avatar_image": "https://gravatar.com/avatar/76a4c5450dbb6fde8a293a811622aa6f?s=250&d=blank",\ "email_count": 0,\ "email_opened_count": 0,\ "email_open_rate": null,\ "status": "free",\ "last_seen_at": "2022-05-20T16:29:29.000Z",\ "newsletters": [\ {\ "id": "62750bff2b868a34f814af08",\ "name": "My Ghost Site",\ "description": null,\ "status": "active"\ }\ ]\ },\ ...\ ] } ### Subscription object A paid member includes a subscription object that provides subscription details. // Subscription object [\ {\ "id": "sub_1KlTkYSHlkrEJE2dGbzcgc61",\ "customer": {\ "id": "cus_LSOXHFwQB7ql18",\ "name": "Jamie",\ "email": "jamie@ghost.org"\ },\ "status": "active",\ "start_date": "2022-04-06T07:57:58.000Z",\ "default_payment_card_last4": "4242",\ "cancel_at_period_end": false,\ "cancellation_reason": null,\ "current_period_end": "2023-04-06T07:57:58.000Z",\ "price": {\ "id": "price_1Kg0ymSHlkrEJE2dflUN66EW",\ "price_id": "6239692c664a9e6f5e5e840a",\ "nickname": "Yearly",\ "amount": 100000,\ "interval": "year",\ "type": "recurring",\ "currency": "USD"\ },\ "tier": {...},\ "offer": null\ }\ ] | Key | Description | | --- | --- | | **customer** | Stripe customer attached to the subscription | | **start\_date** | Subscription start date | | **default\_payment\_card\_last4** | Last 4 digits of the card | | **cancel\_at\_period\_end** | If the subscription should be canceled or renewed at period end | | **cancellation\_reason** | Reason for subscription cancellation | | **current\_period\_end** | Subscription end date | | **price** | Price information for subscription including Stripe price ID | | **tier** | Member subscription tier | | **offer** | Offer details for a subscription | ### Creating a member At minimum, an email is required to create a new, free member. // POST /admin/members/ { "members": [\ {\ "email": "jamie@ghost.org",\ }\ ] } // Response { "members": [\ {\ "id": "624d445026833200a5801bce",\ "uuid": "83525d87-ac70-40f5-b13c-f9b9753dcbe8",\ "email": "jamie@ghost.org",\ "name": null,\ "note": null,\ "geolocation": null,\ "created_at": "2022-04-06T07:42:08.000Z",\ "updated_at": "2022-04-06T07:42:08.000Z",\ "labels": [],\ "subscriptions": [],\ "avatar_image": "https://gravatar.com/avatar/7d8efd2c2a781111599a8cae293cf704?s=250&d=blank",\ "email_count": 0,\ "email_opened_count": 0,\ "email_open_rate": null,\ "status": "free",\ "last_seen_at": null,\ "tiers": [],\ "newsletters": []\ }\ ] } Additional writable member fields include: | Key | Description | | --- | --- | | **name** | member name | | **note** | notes on the member | | **labels** | member labels | | **newsletters** | List of newsletters subscribed to by this member | Create a new, free member with name, newsletter, and label: // POST /admin/members/ { "members": [\ {\ "email": "jamie@ghost.org",\ "name": "Jamie",\ "labels": [\ {\ "name": "VIP",\ "slug": "vip"\ }\ ],\ "newsletters": [\ {\ "id": "624d445026833200a5801bce"\ }\ ]\ }\ ] } ### Updating a member PUT /admin/members/{id}/ All writable fields of a member can be updated. It’s recommended to perform a `GET` request to fetch the latest data before updating a member. Below is a minimal example for updating the name of a member: // PUT /admin/members/{id}/ { "members": [\ {\ "name": "Jamie II"\ }\ ] } Users ----- The users resource provides an endpoint for fetching and editing staff user data. Fetch users (by default, the 15 newest staff users are returned): // GET /admin/users/?include=count.posts%2Cpermissions%2Croles%2Croles.permissions { "id": "1", "name": "Jamie Larson", "slug": "jamie", "email": "jamie@example.com", "profile_image": "http://localhost:2368/content/images/1970/01/jamie-profile.jpg", "cover_image": null, "bio": null, "website": null, "location": null, "facebook": null, "twitter": null, "accessibility": null, "status": "active", "meta_title": null, "meta_description": null, "tour": null, "last_seen": "1970-01-01T00:00:00.000Z", "comment_notifications": true, "free_member_signup_notification": true, "paid_subscription_started_notification": true, "paid_subscription_canceled_notification": false, "mention_notifications": true, "milestone_notifications": true, "created_at": "1970-01-01T00:00:00.000Z", "updated_at": "1970-01-01T00:00:00.000Z", "permissions": [], "roles": [{\ "id": "64498c2a7c11e805e0b4ad4f",\ "name": "Owner",\ "description": "Site Owner",\ "created_at": "1970-01-01T00:00:00.000Z",\ "updated_at": "1970-01-01T00:00:00.000Z",\ "permissions": []\ }], "count": { "posts": 1 }, "url": "http://localhost:2368/author/jamie/" }, ... ] } Note that the Owner user does not have permissions assigned to it, or to the Owner role. This is because the Owner user has _all_ permissions implicitly. ### Roles The roles resource provides an endpoint for fetching role data. // GET /admin/roles/ { "roles": [\ {\ "id": "64498c2a7c11e805e0b4ad4b",\ "name": "Administrator",\ "description": "Administrators",\ "created_at": "1920-01-01T00:00:00.000Z",\ "updated_at": "1920-01-01T00:00:00.000Z"\ },\ ...\ ] } ### Invites The invites resource provides an endpoint for inviting staff users to the Ghost instance. To invite a user you must specify the ID of the role they should receive (fetch roles, detailed above, to find the role IDs for your site), and the email address that the invite link should be sent to. Create a staff user invite: // POST /admin/invites/ { "invites": [\ {\ "role_id": "64498c2a7c11e805e0b4ad4b",\ "email": "person@example.com"\ },\ ...\ ] } ### Updating a user PUT /admin/users/{id}/ All writable fields of a user can be updated. It’s recommended to perform a `GET` request to fetch the latest data before updating a user. Below is a minimal example for updating the name of a user: // PUT /admin/users/{id}/ { "users": [\ {\ "name": "Cameron Larson"\ }\ ] } ### Deleting a user DELETE /admin/users/{id}/ This will delete the user. Note: You cannot delete the Owner user. Images ------ Sending images to Ghost via the API allows you to upload images one at a time, and store them with a [storage adapter](/integrations/?tag=storage) . The default adapter stores files locally in /content/images/ without making any modifications, except for sanitising the filename. POST /admin/images/upload/ ### The image object Images can be uploaded to, and fetched from storage. When an image is uploaded, the response is an image object that contains the new URL for the image - the location from which the image can be fetched. `url`: _URI_ The newly created URL for the image. `ref`: _String (optional)_ The reference for the image, if one was provided with the upload. // POST /admin/images/upload/ { "images": [\ {\ "url": "https://demo.ghost.io/content/images/2019/02/ghost-logo.png",\ "ref": "ghost-logo.png"\ }\ ] } ### Uploading an Image To upload an image, send a multipart formdata request by providing the `'Content-Type': 'multipart/form-data;'` header, along with the following fields encoded as [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) : `file`: _[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or [File](https://developer.mozilla.org/en-US/docs/Web/API/File) _ The image data that you want to upload. `purpose`: _String (default: `image`)_ Intended use for the image, changes the validations performed. Can be one of `image` , `profile_image` or `icon`. The supported formats for `image`, `icon`, and `profile_image` are WEBP, JPEG, GIF, PNG and SVG. `profile_image` must be square. `icon` must also be square, and additionally supports the ICO format. `ref`: _String (optional)_ A reference or identifier for the image, e.g. the original filename and path. Will be returned as-is in the API response, making it useful for finding & replacing local image paths after uploads. curl -X POST -F 'file=@/path/to/images/my-image.jpg' -F 'ref=path/to/images/my-image.jpg' -H "Authorization: 'Ghost $token'" -H "Accept-Version: $version" https://{admin_domain}/ghost/api/admin/images/upload/ Themes ------ Themes can be uploaded from a local ZIP archive and activated. POST /admin/themes/upload; PUT /admin/themes/{ name }/activate; ### The theme object When a theme is uploaded or activated, the response is a `themes` array containing one theme object with metadata about the theme, as well as its status (active or not). `name`: _String_ The name of the theme. This is the value that is used to activate the theme. `package`: _Object_ The contents of the `package.json` file is exposed in the API as it contains useful theme metadata. `active`: _Boolean_ The status of the theme showing if the theme is currently used or not. `templates`: _Array_ The list of templates defined by the theme. // POST /admin/images/upload/ { themes: [{\ name: "Alto-master",\ package: {...},\ active: false,\ templates: [{\ filename: "custom-full-feature-image",\ name: "Full Feature Image",\ for: ["page", "post"],\ slug: null\ }, ...]\ }] } ### Uploading a theme To upload a theme ZIP archive, send a multipart formdata request by providing the `'Content-Type': 'multipart/form-data;'` header, along with the following field encoded as [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) : `file`: _[Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or [File](https://developer.mozilla.org/en-US/docs/Web/API/File) _ The theme archive that you want to upload. curl -X POST -F 'file=@/path/to/themes/my-theme.zip' -H "Authorization: Ghost $token" -H "Accept-Version: $version" https://{admin_domain}/ghost/api/admin/themes/upload Site ---- Site is a special unauthenticated, read-only endpoint for retrieving basic information about a site. This information is useful for integrations and clients that need to show some details of a site before providing authentication. GET /admin/site/ ### The site object The site endpoint returns a single object, rather than an array. `title`: _String_ The title of the site, same as the title returned from the `settings` endpoint. `description`: _String_ The description of the site, same as the description returned from the `settings` endpoint. `logo`: _String_ The logo of the site, provided as a relative path. Same as the logo returned from the `settings` endpoint. `url`: _URI_ The frontend URL for the site, which can be different to the Ghost Admin / API URL. This comes from the configuration JSON file. `version`: _Semver String (major.minor)_ The current version of the Ghost site. Use this to check the minimum version is high enough for compatibility with integrations. // GET admin/site/ { "site": { "title": "Ghost", "description": "The professional publishing platform", "logo": "/content/images/2014/09/logo.png", "url": "https://demo.ghost.io/", "version": "3.14" } } Webhooks -------- Webhooks allow you to build or set up [custom integrations](/integrations/custom-integrations/#api-webhook-integrations) , which subscribe to certain events in Ghost. When one of such events is triggered, Ghost sends a HTTP POST payload to the webhook’s configured URL. For instance, when a new post is published Ghost can send a notification to configured endpoint to trigger a search index re-build, slack notification, or whole site deploy. For more information about webhooks read [this webhooks reference](/docs/webhooks/) . POST /admin/webhooks/ PUT /admin/webhooks/{id}/ DELETE /admin/webhooks/{id}/ ### The webhook object Webhooks can be created, updated, and removed. There is no API to retrieve webhook resources independently. ### Creating a Webhook POST /admin/webhooks/ Required fields: `event`, `target_url` Conditionally required field: `integration_id` - required if request is done using [user authentication](#user-authentication) Optional fields: `name`, `secret`, `api_version` Below is a minimal example to create a webhook using [token authenticated](#token-authentication) request: // POST /admin/webhooks/ { "webhooks": [{\ "event": "post.added",\ "target_url": "https://example.com/hook/"\ }] } When creating a webhook through [user authenticated](#user-authentication) request, minimal payload would look like following: // POST /admin/webhooks/ { "webhooks": [{\ "event": "post.added",\ "target_url": "https://example.com/hook/",\ "integration_id": "5c739b7c8a59a6c8ddc164a1"\ }] } and example response for both requests would be: { "webhooks": [\ {\ "id": "5f04028cc9b839282b0eb5e3",\ "event": "post.added",\ "target_url": "https://example.com/hook/",\ "name": null,\ "secret": null,\ "api_version": "v5",\ "integration_id": "5c739b7c8a59a6c8ddc164a1",\ "status": "available",\ "last_triggered_at": null,\ "last_triggered_status": null,\ "last_triggered_error": null,\ "created_at": "2020-07-07T05:05:16.000Z",\ "updated_at": "2020-09-15T04:01:07.643Z"\ }\ ] } ### Updating a Webhook PUT /admin/webhooks/{id}/ All writable fields of a webhook can be updated via edit endpoint. These are following fields: * `event` - one of [available events](/docs/webhooks/#available-events) * `target_url` - the target URL to notify when event happens * `name` - custom name * `api_version` - API version used when creating webhook payload for an API resource // PUT admin/webhooks/5f04028cc9b839282b0eb5e3 { "webhooks": [{\ "event": "post.published.edited",\ "name": "webhook example"\ }] } and example response for update requests would be: { "webhooks": [\ {\ "id": "5f04028cc9b839282b0eb5e3",\ "event": "post.published.edited",\ "target_url": "https://example.com/hook/",\ "name": "webhook example",\ "secret": null,\ "api_version": "v",\ "integration_id": "5c739b7c8a59a6c8ddc164a1",\ "status": "available",\ "last_triggered_at": null,\ "last_triggered_status": null,\ "last_triggered_error": null,\ "created_at": "2020-07-07T05:05:16.000Z",\ "updated_at": "2020-09-15T04:05:07.643Z"\ }\ ] } ### Deleting a Webhook DELETE /admin/webhooks/{id}/ Delete requests have no payload in the request or response. Successful deletes will return an empty 204 response. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Contributing to Ghost - Submit your contribution to open source software Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost is completely open source software built almost entirely by volunteer contributors who use it every day. The best part about structuring a software project this way is that not only does everyone get to own the source code without restriction, but as people all over the world help to improve it: Everyone benefits. Core team --------- In addition to [full time product team](/about/) working for Ghost Foundation, there are a number of community members who have contributed to the project for a lengthy period of time and are considered part of the core team. They are: * [Austin Burdine](https://github.com/acburdine) - Ghost-CLI * [Felix Rieseberg](https://github.com/felixrieseberg) - Ghost Desktop * [Vicky Chijwani](https://github.com/vickychijwani) - Ghost Mobile * [David Balderston](https://github.com/dbalders) - Community #### How core team members are added People typically invited to join the Core Team officially after an extended period of successful contribution to Ghost and demonstrating good judgement. In particular, this means having humility, being open to feedback and changing their mind, knowing the limits of their abilities and being able to communicate all of these things such that it is noticed. Good judgement is what produces trust, not quality, quantity or pure technical skill. When we believe a core contributor would make a great ambassador for Ghost and feel able to trust them to make good decisions about its future - that’s generally when we’ll ask them to become a member of the formal Core Team. Core Team members are granted commit rights to Ghost projects, access to the Ghost Foundation private Slack, and occasionally join our international team retreats. Community guidelines -------------------- All participation in the Ghost community is subject to our incredibly straightforward [code of conduct](/conduct/) and wider [community guidelines](https://forum.ghost.org/t/faq-guidelines/5) . The vast majority of the Ghost community is incredible, and we work hard to make sure it stays that way. We always welcome people who are friendly and participate constructively, but we outright ban anyone who is behaving in a poisonous manner. Ghost Trademark --------------- **Ghost** is a registered trademark of Ghost Foundation Ltd. We’re happy to extend a flexible usage license of the Ghost trademark to community projects, companies and individuals, however it please read the **[Ghost trademark usage policy](/trademark/) ** before using the Ghost name in your project. Development guide ----------------- If you’re a developer looking to help, but you’re not sure where to begin: Check out the [good first issue](https://github.com/TryGhost/Ghost/labels/good%20first%20issue) label on GitHub, which contains small pieces of work that have been specifically flagged as being friendly to new contributors. Or, if you’re looking for something a little more challenging to sink your teeth into, there’s a broader [help wanted](https://github.com/TryGhost/Ghost/labels/help%20wanted) label encompassing issues which need some love. When you’re ready, check out the full **[Ghost Contributing Guide](https://github.com/TryGhost/Ghost/blob/main/.github/CONTRIBUTING.md) ** for detailed instructions about how to hack on Ghost Core and send changes upstream. > Ghost is currently hiring Product Engineers! Check out what it’s like to be part of the team and see our open roles at [careers.ghost.org](https://careers.ghost.org/) Other ways to help ------------------ The primary way to contribute to Ghost is by writing code, but if you’re not a developer there are still ways you can help. We always need help with: * Helping our Ghost users on [the forum](https://forum.ghost.org) * Creating tutorials and guides * Testing and quality assurance * Hosting local events or meetups * Promoting Ghost to others There are lots of ways to make discovering and using Ghost a better experience. Donations --------- As a non-profit organisation we’re always grateful to receive any and all donations to help our work, and allow us to employ more people to work on Ghost directly. #### Partnerships We’re very [happy to partner](/partners/) with startups and companies who are able to provide Ghost with credit, goods and services which help us build free, open software for everyone. Please reach out to us `hello@ghost.org` if you’re interested in partnering with us to help Ghost. #### Open Collective [![OpenCollective](https://opencollective.com/ghost/backers/badge.svg)](https://opencollective.com/ghost) **New:** We have a number of ongoing donation and sponsorship opportunities for individuals or companies looking to make ongoing contributions to the open source software which they use on [Open Collective](https://opencollective.com/ghost) . #### Bitcoin For those who prefer to make a one time donation, we’re very happy to accept BTC. Unless you explicitly want your donation to be anonymous, please send us a tweet or an email and let us know who you are! We’d love to say thank you. ![Ghost BTC Address](/images/docs/concepts/btc-wallet_huc4fe22c23acec5bcf43cd862bd3a3a9a_3915_356x0_resize_q100_h2_box_3.webp) **Ghost BTC Address:** `3CrQfpWaZPFfD4kAT7kh6avbW7bGBHiBq9` Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Webhooks Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Webhooks are specific events triggered when something happens in Ghost, like publishing a new post or receiving a new member Overview -------- Webhooks allows Ghost to send POST requests to user-configured URLs in order to send them a notification about it. The request body is a JSON object containing data about the triggered event, and the end result could be something as simple as a Slack notification or as complex as a total redeployment of a site. Setting up a webhook -------------------- Configuring webhooks can be done through the Ghost Admin user interface under `Settings > Advanced > Integrations > Add custom integration`. The only required fields to setup a new webhook are a trigger event and target URL to notify. This target URL is your application URL, the endpoint where the POST request will be sent. Of course, this URL must be reachable from the Internet. If the server responds with 2xx HTTP response, the delivery is considered successful. Anything else is considered a failure of some kind, and anything returned in the body of the response will be discarded. Available events ---------------- Currently Ghost has support for below events on which webhook can be setup: | Event | Description | | --- | --- | | `site.changed` | Triggered whenever any content changes in your site data or settings | | `post.added` | Triggered whenever a post is added to Ghost | | `post.deleted` | Triggered whenever a post is deleted from Ghost | | `post.edited` | Triggered whenever a post is edited in Ghost | | `post.published` | Triggered whenever a post is published to Ghost | | `post.published.edited` | Triggered whenever a published post is edited in Ghost | | `post.unpublished` | Triggered whenever a post is unpublished from Ghost | | `post.scheduled` | Triggered whenever a post is scheduled to be published in Ghost | | `post.unscheduled` | Triggered whenever a post is unscheduled from publishing in Ghost | | `post.rescheduled` | Triggered whenever a post is rescheduled to publish in Ghost | | `page.added` | Triggered whenever a page is added to Ghost | | `page.deleted` | Triggered whenever a page is deleted from Ghost | | `page.edited` | Triggered whenever a page is edited in Ghost | | `page.published` | Triggered whenever a page is published to Ghost | | `page.published.edited` | Triggered whenever a published page is edited in Ghost | | `page.unpublished` | Triggered whenever a page is unpublished from Ghost | | `page.scheduled` | Triggered whenever a page is scheduled to be published in Ghost | | `page.unscheduled` | Triggered whenever a page is unscheduled from publishing in Ghost | | `page.rescheduled` | Triggered whenever a page is rescheduled to publish in Ghost | | `tag.added` | Triggered whenever a tag is added to Ghost | | `tag.edited` | Triggered whenever a tag is edited in Ghost | | `tag.deleted` | Triggered whenever a tag is deleted from Ghost | | `post.tag.attached` | Triggered whenever a tag is attached to a post in Ghost | | `post.tag.detached` | Triggered whenever a tag is detached from a post in Ghost | | `page.tag.attached` | Triggered whenever a tag is attached to a page in Ghost | | `page.tag.detached` | Triggered whenever a tag is detached from a page in Ghost | | `member.added` | Triggered whenever a member is added to Ghost | | `member.edited` | Triggered whenever a member is edited in Ghost | | `member.deleted` | Triggered whenever a member is deleted from Ghost | Stripe webhooks --------------- Webhooks allow Ghost to communicate with Stripe. In order to use Stripe with a local version of Ghost you’ll need to do some additional setup to allow webhook events happen between Stripe and Ghost. First, follow the instructions on [how to install and log into the Stripe CLI tool](https://stripe.com/docs/stripe-cli) in the Stripe documentation. Then, before starting a local instance of Ghost, run the following command in your CLI. Note that the localhost port number should match the one used in your local Ghost install: stripe listen --forward-to http://localhost:2368/members/webhooks/stripe/ After running this the CLI will return a secret prefixed with `whsec_`. This secret needs to be given to Ghost on start up. In a new CLI window run the following: WEBHOOK_SECRET=whsec_1234567890abcdefg ghost start After following these steps, Ghost will run locally with a webhook connection to your Stripe account. To test that it’s working, sign up for a paid membership on the local site. Now that the local install of Ghost is running and communicating with Stripe, you can develop and test themes for a custom membership experience, build signup and signin forms, or expose member data. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) A catalog of critical changes between major Ghost versions New major versions typically involve some backwards incompatible changes. These mostly affect custom themes, and our theme compatibility tool [GScan](/docs/themes/gscan/) will guide you through the updates. If you use custom integrations,the API or webhooks you should also expect things to change when switching between [API versions](/docs/faq/api-versioning) . #### How to update? The [update guide](/docs/update/) explains how to update from Ghost 1.0 or higher to the **latest version**. Ghost(Pro) customers should use the [update guide for Ghost (Pro)](/help/how-to-upgrade-ghost/) . #### When to update? The best time to do a [major version](/docs/faq/major-versions-lts) update is shortly after the first minor version - so for Ghost 5.x, the best time to update will be when 5.1.0 is released, which is usually a week or two after the first 5.x release. This is when any bugs or unexpected compatibility issues have been resolved but the [team & community](https://forum.ghost.org) are still context loaded about the changes. The longer you hold off, the bigger the gap becomes between the software you are using and the latest version. Mobiledoc deprecation --------------------- With the release of the [new editor](https://ghost.org/changelog/editor-beta/) , Ghost uses [Lexical](https://lexical.dev/) to store post content, which replaces the previous format Mobiledoc. Transitioning to Lexical enables Ghost to build new powerful features that weren’t possible with Mobiledoc. To remain compatible with Ghost, integrations that rely on Mobiledoc should switch to using Lexical. [For more resources on working with Lexical, see their docs](https://lexical.dev/docs/intro) . Ghost 5.0 --------- Ghost 5.0 includes significant changes to the Ghost API and database support to ensure optimal performance. #### Supported databases **MySQL 8** is the only supported database for both development and production environments. * SQLite3 is supported only in development environments where scalability and data consistency across updates is not critical (during local theme development, for example) * MySQL 5 is no longer supported in any environment Note: MariaDB is not an officially supported database for Ghost. #### Portal If you’re embedding portal on an external site, you’ll need to update your script tag. You can generate a Content API key and check your API url in the Custom Integration section in Ghost Admin. For more information see the [Content API docs](/docs/content-api/) . #### Themes Themes can be validated against 5.x in [GScan](https://gscan.ghost.org) . * Card assets will now be included by default, including bookmark and gallery cards. ([docs](/docs/themes/helpers/config/) ) * Previously deprecated features have been removed: `@blog`, single authors. ##### Custom membership flows The syntax used to build custom membership flows has changed significantly. * Tier benefits are now returned as a list of strings. ([docs](/docs/themes/helpers/tiers/#fetching-tiers-with-the-get-helper) ) * Paid Tiers now have numeric `monthly_price` and `yearly_price` attributes, and a separate `currency` attribute. ([docs](/docs/themes/helpers/tiers/) ) * The following legacy product and price helpers used to build custom membership flows have been removed: `@price`, `@products`, `@product` and `@member.product`. See below for examples of the new syntax for building a custom signup form and account page. ([docs](/docs/themes/members/#member-subscriptions) ) **Sign up form** {{! Fetch all available tiers }} {{#get "tiers" limit="all" include="monthly_price,yearly_price,benefits"}} {{#foreach tiers}}

{{name}}

{{! Output tier name }}

{{description}}

{{! Output tier description }} {{#if monthly_price}} {{! If tier has a monthly price, generate a Stripe sign up link }} Monthly – {{price monthly_price currency=currency}} {{/if}} {{#if yearly_price}} {{! If tier has a yearly price, generate a Stripe sign up link }} Monthly – {{price yearly_price currency=currency}} {{/if}}

    {{#foreach benefits as |benefit| }} {{! Output list of benefits }}
  • {{benefit}}
  • {{/foreach}}
{{/foreach}} {{/get}} **Account page**

{{@member.name}}

{{@member.email}}

{{#foreach @member.subscriptions}}

Tier name: {{tier.name}}

Subscription status: {{status}}

Amount: {{price plan numberFormat="long"}}/{{plan.interval}}

Start date: {{date start_date}}

End date: {{date current_period_end}}

{{cancel_link}} {{! Generate a link to cancel the membership }} {{/foreach}} #### API versioning Ghost 5.0 no longer includes multiple API versions for backwards compatibility with previous versions. The URLs for the APIs are now `ghost/api/content` and `ghost/api/admin`. Breaking changes will continue to be made only in major versions; new features and additions may be added in minor version updates. Backwards compatibility is now provided by sending an `accept-version` header with API requests specifying the compatibility version a client expects. When this header is present in a request, Ghost will respond with a `content-version` header indicating the version that responded. In the case that the provided `accept-version` is below the minimum version supported by Ghost and a request either cannot be served or has changed significantly, Ghost will notify the site’s administrators via email informing them of the problem. Requests to the old, versioned URLs are rewritten internally with the relevant `accept-version` header set. These requests will return a `deprecation` header. #### Admin API changes * The `/posts` and `/pages` endpoints no longer accept `page:(true|false)` as a filter in the query parameters * The `email_recipient_filter` and `send_email_when_published` parameters have been removed from the `/posts` endpoint, and email sending is now controlled by the new `newsletter` and `email_segment` parameters * The `/mail` endpoint has been removed * The `/email_preview` endpoint has been renamed to `/email_previews` * The `/authentication/reset_all_passwords` endpoint has been renamed to `/authentication/global_password_reset` and returns a `204 No Content` response on success * The `/authentication/passwordreset` endpoint has been renamed to `/authentication/password_reset`, and accepts and returns a `password_reset` object * The `DELETE /settings/stripe/connect` endpoint now returns a `204 No Content` response on success * The `POST /settings/members/email` endpoint now returns a `204 No Content` response on success #### Content API changes * The `GET /posts` and `GET /pages` endpoints no longer return the `page:(true|false)` attribute in the response #### Members * The `members/api/site` and `members/api/offers` endpoints have been removed, and Portal now uses the Content API * All `/products/*` endpoints have been replaced with `/tiers/*`, and all references to `products` in requests/responses have been updated to use `tiers` * Tier benefits are now returned as a list of strings * Paid Tiers now have numeric `monthly_price` and `yearly_price` attributes, and a separate `currency` attribute * The member `subscribed` flag has been deprecated in favor of the `newsletters` relation, which includes the newsletters a member is subscribed to #### Misc * Removed support for serving secure requests when `config.url` is set to `http` * Removed support for configuring the server to connect to a socket instead of a port * Deleting a user will no longer remove their posts, but assign them to the site owner instead * Site-level email design settings have been replaced with design settings on individual newsletters (see [`/newsletters/*` endpoints](/docs/admin-api/#newsletters) ) Ghost 4.0 --------- Ghost 4.0 focuses on bringing Memberships out of beta. There are a few additional changes: * New `/v4/` (stable) and `/canary/` (experimental) API versions have been added. * The `/v3/` (maintenance) endpoints will not receive any further changes. * The `/v2/` (deprecated) endpoints will be removed in the next major version. * v4 Admin API `/settings/` endpoint no longer supports the `?type` query parameter. * v4 Admin API `/settings/` endpoint only accepts boolean values for the key `unsplash`. * Redirects: definitions should now be uploaded in YAML format - `redirects.json` has been deprecated in favour of `redirects.yaml`. * Themes: **must** now define which version of the API they want to use by adding `"engines": {"ghost-api": "vX"}}` to the `package.json` file. * Themes: due to content images having `width` / `height` attributes, themes with CSS that use `max-width` may need to add `height: auto` to prevent images appearing squashed or stretched. * Themes: The default format for the `{{date}}` helper is now a localised short date string (`ll`). * Themes: `@site.lang` has been deprecated in favour of `@site.locale`. * Private mode: the cookie has been renamed from `express:sess` to `ghost-private`. * Other: It’s no longer possible to require or use Ghost as an NPM module. ### Members Members functionality is no longer considered beta and is always enabled. The following are breaking changes from the behaviour in Ghost 3.x: * v3/v4 Admin API `/members/` endpoint no longer supports the `?paid` query parameter * v3/v4 Admin API `/members/` endpoints now have subscriptions on the `subscriptions` key, rather than `stripe.subscriptions`. * v3/v4 Admin API `/posts/` endpoint has deprecated the `send_email_when_published` flag in favour of `email_recipient_filter`. * Themes: The `@labs.members` theme helper always returns `true`, and will be removed in the next major version. * Themes: The default post visibility in `foreach` in themes is now `all`. * Themes: The `default_payment_card_last4` property of member subscriptions now returns `****` instead of `null` if the data is unavailable. * Portal: query parameters no longer use `portal-` prefixes. * Portal: the root container has been renamed from `ghost-membersjs-root` to `ghost-portal-root`. * Other: Stripe keys are no longer included in exports. * Other: Using Stripe related features in a local development environment requires `WEBHOOK_SECRET`, and live stripe keys are no longer supported in non-production environments. Ghost 3.0 --------- * The Subscribers labs feature has been replaced with the [Members](/docs/members/) labs feature. * The v0.1 API endpoints & Public API Beta have been removed. Ghost now has a set of fully supported [Core APIs](/docs/architecture/) . * The Apps beta concept has been removed. Use the Core APIs & [integrations](/integrations/) instead. * Themes using [GhostHunter](https://github.com/jamalneufeld/ghostHunter) must upgrade to [GhostHunter 0.6.0](https://github.com/jamalneufeld/ghostHunter#ghosthunter-v060) . * Themes using `ghost.url.api()` must upgrade to the [Content API client library](/docs/content-api/javascript/) . * Themes may be missing CSS for editor cards added in 2.x. Use [GScan](https://gscan.ghost.org/) to make sure your theme is fully 3.0 compatible. * Themes must replace `{{author}}` for either `{{#primary_author}}` or `{{authors}}`. * New `/v3/` (stable) and `/canary/` (experimental) API versions have been added. * The `/v2/` (maintenance) endpoints will not receive any further changes. * v3 Content API `/posts/` & `/pages/` don’t return `primary_tag` or `primary_author` when `?include=tags,authors` isn’t specified (these were returned as null previously). * v3 Content API `/posts/` & `/pages/` no longer return page: `true|false`. * v3 Content + Admin API `/settings/` no longer returns ghost\_head or `ghost_foot`, use `codeinjection_head` and `codeinjection_foot` instead. * v3 Admin API `/subscribers/*` endpoints are removed and replaced with `/members/*`. * v3 Content + Admin API consistently stores relative and serves absolute URLs for all images and links, including inside content & srcsets. ### Switching from v0.1 API * The Core APIs are stable, with both read & write access fully supported. * v0.1 Public API (read only access) is replaced by the [Content API](/docs/content-api/) . * v0.1 Private API (write access) is replaced by the [Admin API](/docs/admin-api/) . * v0.1 Public API `client_id` and `client_secret` are replaced with a single `key`, found by configuring a new Custom Integration in Ghost Admin. * v0.1 Public API `ghost-sdk.min.js` and `ghost.url.api()` are replaced with the `@tryghost/content-api` [client library](/docs/content-api/javascript/) . * v0.1 Private API client auth is replaced with JWT auth & user auth now uses a session cookie. The `@tryghost/admin-api` [client library](/docs/admin-api/javascript/) supports easily creating content via JWT auth. * Scripts need updating to handle API changes, e.g. posts and pages being served on separate endpoints and users being called authors in the Content API. Ghost 2.0 --------- * API: The `/v2/` API replaces the deprecated `/v0.1/` API. * Themes: The editor has gained many new features in 2.x, you may need to add CSS to your theme for certain cards to display correctly. * Themes: `{{#get "users"}}` should be replaced with `{{#get "authors"}}` * Themes: multiple authors are now supported, swap uses of author for either `{{#primary_author}}` or `{{authors}}`. * Themes: can now define which version of the API they want to use by adding `"engines": {"ghost-api": "vX"}}` to the `package.json` file. * Themes: there are many minor deprecations and warnings, e.g. `@blog` has been renamed to `@site`, use [GScan](https://gscan.ghost.org) to make sure your theme is fully 2.0 compatible. * v2 Content+Admin API has split `/posts/` & `/pages/` endpoints, instead of just `/posts/`. * v2 Content API has an `/authors/` endpoint instead of `/users/`. * v2 Admin API `/posts/` and `/pages/` automatically include tags and authors without needing `?includes=`. * v2 Content + Admin API attempts to always save relative & serve absolute urls for images and links, but this behaviour is inconsistent 🐛. Ghost 1.0 --------- * This is a major upgrade, with breaking changes and no automatic migration path. All publications upgrading from Ghost 0.x versions must be [upgraded](/docs/faq/update-0x/) to Ghost 1.0 before they can be successfully upgraded to Ghost 2.0 and beyond. * See [announcement post](/changelog/1-0/) and [developer details](/changelog/ghost-1-0-0/) for full information on what we changed in 1.0. * v0.1 Public API `/shared/ghost-url.min.js` util has been moved and renamed to `/public/ghost-sdk.min.js` * Ghost 0.11.x exports don’t include `clients` and `trusted_domains` so these aren’t imported to your new site - you’ll need to update any scripts with a new `client_id` and `client_secret` from your 1.0 install. * Themes: Many image fields were renamed, use [GScan](https://gscan.ghost.org) to make sure your theme is 1.0 compatible. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Frequently asked questions and answers about running Ghost [API versioning\ --------------\ \ Ghost ships with a mature set of APIs. Each API endpoint has a status, which indicates suitability for production use. Read more about Ghost’s architecture. Stability Index Stable - forward compatible changes only Experimental - being actively worked on, may receive breaking changes Deprecated - scheduled for removal Accept-Version Header Accept-Version: v{major}.{minor} The ‘Accept-Version’ header allows clients to indicate the minimum version of Ghost they can operate with. It also allows Ghost to determine and communicate if any recent breaking changes are unsuitable for a given client.](/docs/faq/api-versioning/) [Clustering, sharding, HA and other multi-server setups\ ------------------------------------------------------\ \ Ghost doesn’t support load-balanced clustering or multi-server setups of any description, there should only be one Ghost instance per site. The recommended approach to achieve scale, performance & high availability is to put a cache and/or CDN in front of your blog; pages generated by Ghost are essentially static so there should be very little traffic hitting your Ghost server with a well configured cache. Error If you try to run Ghost on multiple nodes, you will run into errors such as Response code 405 (Method Not Allowed) - and find that the Ghost service needs to be constantly restarted for any content to reliably appear.](/docs/faq/clustering-sharding-multi-server/) [Filter property not working in routing\ --------------------------------------\ \ Working with more complex iterations of the filter property in the routes.yaml file can cause conflicts or unexpected behaviour. Here are the most common issues. Specify inverse filters In the current beta version of Dynamic Routing it’s necessary to specify the inverse of filters using - to ensure pagination is correct. For example: collections: /es/: permalink: /es/{slug}/ filter: tag:es /fr/: permalink: /fr/{slug}/ filter: tag:fr /: permalink: /{slug}/ filter: tag:-\[fr,es\] Order filters In a collection, the post lives in the first collection where it matches the filter, so it’s important to consider the order of your filters and place more generic filters towards the end.](/docs/faq/filter-property-routes-yaml/) [How can I backup my site data?\ ------------------------------\ \ Learn how to backup your self-hosted Ghost install When performing manual updates it’s always recommended to make a full backup of your site first, so if anything goes wrong, you’ll still have all your data. The fastest way to perform a backup is to use Ghost CLI to automatically generate a zip file containing all of your site data using the ghost backup command. If you need to perform a manual backup of your data, the following guide walks you through all of the steps.](/docs/faq/manual-backup/) [How to resolve errors when running ghost start\ ----------------------------------------------\ \ If an error occurs when trying to run ghost start or ghost restart, try using ghost run first to check that Ghost can start successfully. The start and restart commands are talking to your process manager (e.g. systemd) which can hide underlying errors from Ghost. Find your Ghost log files in /content/logs/ to identify if the problem exists with Ghost itself, or the process manager.](/docs/faq/errors-running-ghost-start/) [Image upload issues\ -------------------\ \ Image uploads can be affected by the default max upload size of 50mb. If you need more, you’ll need to increase the limit by editing your nginx config file, and setting the limit manually. Troubleshooting Log into your server Navigate to your Ghost installation folder with cd /var/www/ghost. Type: nano system/files/your-domain.conf to open your NGINX config file. Edit client\_max\_body\_size {VALUE}M; Finally, press ctrl + x to exit. Nano will ask you if you want to save, type y for yes, and press enter to save the file.](/docs/faq/image-upload-issues/) [Mail config error in Ghost with Google Cloud\ --------------------------------------------\ \ There’s a known issue that Google Cloud Platform does NOT allow any traffic on port 25 on a Compute Engine instance. To fix this, explicitly set the transmission port to 2525 in order to send emails. Alternatively, we highly recommend using Mailgun which allows up to 10,000 emails per month for free.](/docs/faq/mail-config-error-google-cloud/) [Major Versions & Long Term Support\ ----------------------------------\ \ Major version release dates and end of life support for Ghost. Version Released End of Life Ghost 0.x 2013 Jan 2019 Ghost 1.x 2017 Jan 2020 Ghost 2.x 2018 Jan 2021 Ghost 3.x 2019 Jan 2022 Ghost 4.x 2021 Jan 2023 Ghost 5.x (Current) 2022 TBC At any one time the current major version of Ghost is under active development, whilst the previous version is maintained as the Long Term Support version (LTS).](/docs/faq/major-versions-lts/) [Missing newsletter analytics\ ----------------------------\ \ Open rates that are 0% may indicate that the connection between Ghost and Mailgun has stalled, which prevents Ghost from fetching your newsletter analytics. To recover your newsletter analytics, restart Ghost by running ghost restart from the command line. Restart Ghost as soon as possible because Mailgun logs are only kept for a limited time, after which the logs are no longer recoverable. It’s also best practice to restart Ghost when no newsletters are scheduled to be sent.](/docs/faq/missing-newsletter-analytics/) [Missing SSL protocol\ --------------------\ \ After installing Ghost a url for your site is set. This is the URL people will use to access your publication. Error If you see an error of ERROR: URL in config must be provided with protocol this means the URL has been set with HTTPS but the SSL protocol is missing. To fix Use ghost setup ssl in the ghost-cli to setup an SSL certificate for your publication.](/docs/faq/missing-ssl-protocol/) [Reverse proxying to Ghost\ -------------------------\ \ Ghost is designed to have a reverse proxy in front of it. If you use Ghost-CLI to install Ghost, this will be setup for you using nginx. If you configure your own proxy, you’ll need to make sure the proxy is configured correctly. The configuration used by Ghost CLI looks like this: location / { proxy\_set\_header X-Forwarded-For $proxy\_add\_x\_forwarded\_for; proxy\_set\_header X-Forwarded-Proto $scheme; proxy\_set\_header X-Real-IP $remote\_addr; proxy\_set\_header Host $http\_host; proxy\_pass http://127.0.0.1:2368; } client\_max\_body\_size 50m; If you run into an infinite redirect loop when trying to configure your own proxy with nginx, apache, cloudfront or any other proxy and serving Ghost over HTTPS, this is due to the x-forwarded-proto header being set incorrectly.](/docs/faq/proxying-https-infinite-loops/) [Root user permissions fix\ -------------------------\ \ A fix for root user permissions problems Error We discovered that you are using the DigitalOcean One-Click install. You need to create a user with regular account privileges and migrate your installation to this user. Solution When you installed Ghost with the DigitalOcean One-Click install you probably set up your Ghost installation as a root user. As of Ghost-CLI 1.5.0, running Ghost commands as root is disallowed. Therefore you will need to migrate your current installation to a new Linux user.](/docs/faq/root-user-fix/) [Salt Incident Report: May 3rd, 2020\ -----------------------------------\ \ Analysis and retrospective of the critical Salt vulnerability on Ghost(Pro) Last Sunday, Ghost(Pro) - along with several thousand other services around the world - experienced an incident where a virus used to mine cryptocurrency was able to successfully infect servers within our private network. No customer data was accessed; however as a precaution we revoked all keys, sessions, passwords and certificates - and introduced additional firewalls throughout our network. We subsequently began work to rebuild every server in our network.](/docs/faq/salt-incident/) [Supported Node versions\ -----------------------\ \ Ghost’s current recommended Node version is Node v20 LTS. Version Support Level 17.x and below Unsupported 18.x (Node v18 Hydrogen LTS) Supported 19.x Unsupported 20.x (Node v20 Iron LTS) Recommended 21.x Unsupported 22.x (Node v22 Jod LTS) Supported 23.x and above Unsupported We use the recommended version of Node.js in production on Ghost(Pro), which means it’s heavily tested and the Ghost core team actively fixes issues. Running Ghost on the latest version of Node.](/docs/faq/node-versions/) [Supported providers for self-hosting\ ------------------------------------\ \ We recommend using Digital Ocean who provide a stable option on which Ghost can be installed and have a very active community and an official Ghost One-Click Application. Other providers which will work with Ghost include: Amazon EC2 Google Cloud Linode Vultr Dreamhost Most other VPS providers offer servers that are suitable for installing Ghost with a few exceptions: Auto-installers such as Softaculous Shared/cPanel hosting designed for PHP applications](/docs/faq/supported-hosting-providers/) [Translation in Ghost\ --------------------\ \ Creators from all over the world use Ghost. Publications abound in German, French, Spanish, Sinhalese, and Arabic—and the list keeps going! Below, we have collected together essential concepts, strategies, and known limitations when working with languages other than English in Ghost. Theme translation Ghost fully supports the ability to translate themes into different languages. This means that text in a theme is translated based on the language set in Ghost Admin.](/docs/faq/translation/) [Troubleshooting MySQL databases\ -------------------------------\ \ If your MySQL database is not correctly configured for Ghost, then you may run into some issues. The solutions given are for self-hosted Ghost developers who are using the supported install method with ghost-cli. If you’re having problems with an unsupported custom install, check out the forum. Error ECONNREFUSED If you’re seeing an ECONNREFUSED error, which refers to port 3306, Ghost wasn’t able to connect to your MySQL server and you need to check if your server is running via the command line.](/docs/faq/troubleshooting-mysql-database/) [Unable to open sqlite3 database file\ ------------------------------------\ \ If the sqlite3 database file is not readable or writable by the user running Ghost, then you’ll run into some errors. Error Unable to open sqlite3 database file for read/write (exit code 236) To fix By default, this file exists in content/data/ but this path may also be configured in your config file. Ensure that the path in the config is correct and that the path is both readable and writable by the user running Ghost.](/docs/faq/unable-to-open-sqlite3-database-file/) [Update from Ghost 0.x versions\ ------------------------------\ \ If you’re running Ghost 0.x versions, your site must be updated to Ghost 1.0 before it can be successfully updated to Ghost 2.0 and beyond. It’s recommended to update to the latest version of Ghost to keep your site and data secure, and gain access to the latest features in Ghost. For a successful upgrade, ensure that Ghost, Ghost CLI, and Node are all on compatible versions. Use our Node Compatibility Matrix as a reference.](/docs/faq/update-0x/) [Updating from deprecated Ghost-CLI\ ----------------------------------\ \ When managing your self-hosted Ghost publication using the recommended ghost-cli tooling, you should update your CLI version. If you are using a deprecated version and need to update in order to update or manage your Ghost site, some extra steps may be required. Use the following troubleshooting guide if you run into issues because you are using a deprecated version of the Ghost-CLI. Upgrading from <=1.1.3 Ghost-CLI 1.2.0 added a fix, and Ghost-CLI 1.](/docs/faq/upgrading-from-deprecated-ghost-cli/) [URL for tags and authors returns 404 errors\ -------------------------------------------\ \ The tag and author taxonomies must be present in routes.yaml otherwise the URLs will not exist. By default, Ghost installs with the following: taxonomies: tag: /tag/{slug}/ author: /author/{slug}/ The permalink for tag and author can be amended, but taxonomies for tag and author must always be present for the URL for individual tags and authors to function correctly.](/docs/faq/url-for-tags-and-authors-returns-404/) [Using Cloudflare with Ghost\ ---------------------------\ \ If you’ve added Cloudflare to your self-hosted Ghost publication and find that Ghost Admin doesn’t load after updates you may run into some errors in the JavaScript console: Error Open developer tools to check for errors in the JavaScript code: TypeError: n.item is not a function or Uncaught SyntaxError: Unexpected string To fix It is recommended that you add a page rule which turns off some RocketLoader, mirage2 and autominify for the path /ghost\*, so these features don’t affect the Ghost admin panel.](/docs/faq/using-cloudflare-with-ghost/) [Using nvm with local and production installs\ --------------------------------------------\ \ This guide explains how to use nvm with local and production Ghost installs. Production install If nvm is installed locally in /root or /home then you’ll run into permission problems. Ghost requires a system wide installation. We recommend uninstalling nvm to avoid permission problems. A symlink from the local installation to usr/bin/node will not work. Uninstall nvm using: rm -rf $NVM\_DIR ~/.npm ~/.bower unset NVM\_DIR; which node; rm -rf {path\_to\_node\_version} Open ~/.](/docs/faq/using-nvm/) [What databases are supported in production?\ -------------------------------------------\ \ MySQL 8 is the only supported database in production. As a small team developing open source software that is free to use, we provide a narrow set of environments that are officially supported to keep maintenance manageable. Ghost is built for use with Ubuntu, MySQL 8, nginx and Node.js LTS. MySQL 8 provides features that are optimal for Ghost installations, including the ability to store JSON, and features that help us deliver performance improvements.](/docs/faq/supported-databases/) [Why do I have to set up Mailgun?\ --------------------------------\ \ Ghost has the ability to deliver posts as email newsletters natively. A bulk-mail provider is required to use this feature and SMTP cannot be used — read more about mail config. Transactional email in Ghost can be configured to send with any SMTP, or another mail service that you prefer, using Ghost’s standard configuration setup. Bulk email delivery for newsletters is a new feature which requires a bulk mail API. Currently the only bulk mail API we support is Mailgun.](/docs/faq/mailgun-newsletters/) Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost is free software released under the [MIT License](http://en.wikipedia.org/wiki/MIT_License) , which pretty much means you can do anything you want with it. The MIT License is one of the most free and open licenses in the world, and does not restrict how you use the software which it’s applied to. We believe open source software should be free. As in free. You are allowed to use Ghost however you like, but any copyright notices remain in tact. With the MIT license you have the same freedom to decide how you want to engage with Open Source software. You decide whether your theme or integration is MIT, GPL, or any license you like. It’s up to you to determine what’s best for your project. We believe giving people a choice is the most powerful statement a license can make. The full license is embedded below, and ships with every single copy of Ghost. * * * The Ghost software license -------------------------- Copyright © 2013-2025 Ghost Foundation 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. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to install & setup Ghost on Ubuntu 20.04 or 22.04 Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) A full guide for installing, configuring and running Ghost on your Ubuntu **20.04** or **22.04** server, for use in production Overview -------- This the official guide for self-hosting Ghost using our recommended stack of Ubuntu 20.04 or 22.04. If you’re comfortable installing, maintaining and updating your own software, this is the place for you. By the end of this guide you’ll have a fully configured Ghost install running in production using MySQL. This install is **not** suitable for [local use](/docs/install/local/) or [contributing](/docs/install/source/) to core. Prerequisites ------------- The officially recommended production installation requires the following stack: * Ubuntu 20.04 or Ubuntu 22.04 * NGINX (minimum of 1.9.5 for SSL) * A [supported version](/docs/faq/node-versions/) of [Node.js](https://nodejs.org) * MySQL 8 * Systemd * A server with at least 1GB memory * A registered domain name Before getting started you should set up a working DNS **A-Record** from you domain, pointing to the server’s IP address. This must be done in advance so that SSL can be configured during setup. * * * Server Setup ------------ This part of the guide will ensure all prerequisites are met for installing the Ghost-CLI. ### Create a new user Open up your terminal and login to your new server as the root user: # Login via SSH ssh root@your_server_ip # Create a new user and follow prompts adduser > Note: Using the user name `ghost` causes conflicts with the Ghost-CLI, so it’s important to use an alternative name. # Add user to superuser group to unlock admin privileges usermod -aG sudo # Then log in as the new user su - ### Update packages Ensure package lists and installed packages are up to date. # Update package lists sudo apt-get update # Update installed packages sudo apt-get upgrade Follow any prompts to enter the password you just created in the previous step. ### Install NGINX Ghost uses an NGINX server and the SSL configuration requires NGINX 1.9.5 or higher. # Install NGINX sudo apt-get install nginx If `ufw` was activated, the firewall allows HTTP and HTTPS connections. Open Firewall: sudo ufw allow 'Nginx Full' ### Install MySQL Next, you’ll need to install MySQL to be used as the production database. # Install MySQL sudo apt-get install mysql-server On newer versions of Ubuntu, the root user created when you install MySQL will by default be configured to use socket-based authentication, meaning that only the root Unix user will be able to authenticate. Ghost does not support this kind of authentication, so you must change the root MySQL user to have a password. Run these commands to make the root user have a password: # Enter mysql sudo mysql # Update permissions ALTER USER 'root'@'localhost' IDENTIFIED WITH 'mysql_native_password' BY ''; # Reread permissions FLUSH PRIVILEGES; # exit mysql exit ### Install Node.js You will need to have a [supported version](/docs/faq/node-versions/) of Node installed system-wide in the manner described below. If you have a different setup, you may encounter problems. # Download and import the Nodesource GPG key sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg sudo mkdir -p /etc/apt/keyrings curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg # Create deb repository NODE_MAJOR=18 # Use a supported version echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list # Run update and install sudo apt-get update sudo apt-get install nodejs -y * * * Install Ghost-CLI ----------------- [Ghost-CLI](/docs/ghost-cli/) is a commandline tool to help you get Ghost installed and configured for use, quickly and easily. The npm module can be installed with `npm` or `yarn`. sudo npm install ghost-cli@latest -g Once installed, you can always run `ghost help` to see a list of available commands. * * * Install Ghost ------------- Once your server is correctly setup and `ghost-cli` is installed, you can install Ghost itself. The following steps are the recommended setup. If you need more fine-grained control, the CLI has [flags and options](/docs/ghost-cli/) that allow you to break down and customise the install steps. ### Create a directory Ghost must be installed in its own directory, with a proper owner and permissions. # Create directory: Change `sitename` to whatever you like sudo mkdir -p /var/www/sitename # Set directory owner: Replace with the name of your user sudo chown : /var/www/sitename # Set the correct permissions sudo chmod 775 /var/www/sitename # Then navigate into it cd /var/www/sitename ### Run the install process Now we install Ghost with one final command. ghost install ### Install questions During install, the CLI will ask a number of questions to configure your site. #### Blog URL Enter the exact URL your publication will be available at and include the protocol for HTTP or HTTPS. For example, `https://example.com`. If you use HTTPS, Ghost-CLI will offer to set up SSL for you. Using IP addresses will cause errors. #### MySQL hostname This determines where your MySQL database can be accessed from. When MySQL is installed on the same server, use `localhost` (press Enter to use the default value). If MySQL is installed on another server, enter the name manually. #### MySQL username / password If you already have an existing MySQL database, enter the the username. Otherwise, enter `root`. Then supply the password for your user. #### Ghost database name Enter the name of your database. It will be automatically set up for you, unless you’re using a **non**\-root MySQL user/pass. In that case the database must already exist and have the correct permissions. #### Set up a ghost MySQL user? (Recommended) If you provided your root MySQL user, Ghost-CLI can create a custom MySQL user that can only access/edit your new Ghost database and nothing else. #### Set up NGINX? (Recommended) Sets NGINX up automatically enabling your site to be viewed by the outside world. Setting up NGINX manually is possible, but why would you choose a hard life? #### Set up SSL? (Recommended) If you used an `https` Blog URL and have already pointed your domain to the right place, Ghost-CLI can automatically set up SSL for you using [Let’s Encrypt](https://letsencrypt.org) . Alternatively you do this later by running `ghost setup ssl` at any time. SSL certification setup requires an email address so that you can be kept informed if there is any issue with your certificate, including during renewal. #### Set up systemd? (Recommended) `systemd` is the recommended process manager tool to keep Ghost running smoothly. We recommend choosing `yes` but it’s possible to set up your own process management. #### Start Ghost? Choosing `yes` runs Ghost, and makes your site work. #### Access and secure your site Your Ghost site is now live! To set up your admin account, go to `https://example.com/ghost`. When you get there, you’ll be prompted to create your account as the site owner. * * * Future maintenance ------------------ Once Ghost is properly set up it’s important to keep it properly maintained and up to date. Fortunately, this is relatively easy to do using Ghost-CLI. Run `ghost help` for a list of available commands, or explore the full [Ghost-CLI documentation](/docs/ghost-cli/) . * * * What to do if the install fails ------------------------------- If an install goes horribly wrong, use `ghost uninstall` to remove it and try again. This is preferable to deleting the folder to ensure no artifacts are left behind. If an install is interrupted or the connection lost, use `ghost setup` to restart the configuration process. For troubleshooting and errors, use the site search and [FAQ section](/docs/faq/) to find information about common error messages. * * * What’s next? ------------ You’re all set! Now you can start customising your site. Check out our range of [tutorials](/tutorials/) or the Ghost [API documentation](/docs/content-api/) depending on which page of this choose-your-own-adventure experience you’d like to subject yourself to next. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) The Docker image for Ghost is an unofficial community package maintained by people within the Ghost developer community. Resources --------- Resources and downloads: * **[Installation steps](https://hub.docker.com/_/ghost/) ** * [Ghost Docker Image on GitHub](https://github.com/docker-library/ghost) * [Reporting issues](https://github.com/docker-library/ghost/issues) * [Play With Docker direct link](http://play-with-docker.com/?stack=https://raw.githubusercontent.com/docker-library/docs/e24f39cddf21560cf0a24f149059ff23640b0f16/ghost/stack.yml) Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Logos Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) The Ghost brand is our pride and joy. We’ve gone to great lengths to make it as beautiful as possible, so we care a great deal about keeping it that way! These guidelines provide all of our official assets and styles, along with details of how to correctly use them. ![GitHub](/images/logos/ghost-logo-dark.png) [Download](/images/logos/ghost-logo-dark.png) ![GitHub](/images/logos/ghost-logo-orb.png)[](/images/logos/ghost-logo-orb.png) ![GitHub](/images/logos/ghost-logo-light.png) [Download](/images/logos/ghost-logo-light.png) ### Ghost colours Light backgrounds and tinted greys, accented with Ghost Green. office-file-text Any use of Ghost brand materials constitutes acceptance of the Ghost [Terms of Service](/terms/) , [Trademark Policy](/trademark/) and these Brand Guidelines, which may be updated from time to time. You fully acknowledge that Ghost Foundation is the sole owner of Ghost trademarks, promise not to interfere with Ghost's rights, and acknowledge that goodwill derived from their use accrues only to Ghost. Ghost may review or terminate use of brand materials at any time. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost API Versioning Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost ships with a mature set of APIs. Each API endpoint has a status, which indicates suitability for production use. Read more about Ghost’s [architecture](/docs/architecture/) . Stability Index --------------- * **Stable** - forward compatible changes only * **Experimental** - being actively worked on, may receive breaking changes * **Deprecated** - scheduled for removal Accept-Version Header --------------------- `Accept-Version: v{major}.{minor}` The ‘Accept-Version’ header allows clients to indicate the minimum version of Ghost they can operate with. It also allows Ghost to determine and communicate if any recent breaking changes are unsuitable for a given client. When the `Accept-Version` header is received, Ghost will respond with a `Content-Version` header indicating the version of the Ghost install that is responding. If the `Accept-Version` header is received and the request cannot be processed, Ghost sends an email to the site’s owner and administrators with the details of the problem, to make it easy to find and fix issues with outdated clients. Ghost [major versions](/docs/faq/major-versions-lts/) ship every 8-12 months, meaning code you write against our API today will be stable for a minimum of 2 years. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Supported node versions for self-hosted installs of Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost’s current recommended Node version is Node v20 LTS. | Version | Support Level | | --- | --- | | 17.x and below | Unsupported | | 18.x (Node v18 Hydrogen LTS) | Supported | | 19.x | Unsupported | | **20.x (Node v20 Iron LTS)** | **Recommended** | | 21.x | Unsupported | | 22.x (Node v22 Jod LTS) | Supported | | 23.x and above | Unsupported | We use the recommended version of Node.js in production on Ghost(Pro), which means it’s heavily tested and the Ghost core team actively fixes issues. Running Ghost on the latest version of Node.js is not guaranteed to work, and we can’t offer support for any issues. Ghost is a small team so we keep version support to a minimum to give us time to build new features 🏃‍♀️ Compatible versions of Node.js can be downloaded from the Node.js releases page. You can also install multiple versions of Node using nave or nvm. Upgrade Node.js --------------- When upgrading Node.js, you need to run the update for Node and then re-install Ghost’s dependencies. This is because Ghost has several binary dependencies that are compiled for the specific Node.js version. Without reinstalling dependencies, Ghost will fail to start with strange error messages. To upgrade the steps are: 1. Download and import the Nodesource GPG key sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg sudo mkdir -p /etc/apt/keyrings curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg 2. Create deb repository NODE_MAJOR=20 # Use a supported version echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list 3. Run update and install sudo apt-get update sudo apt-get install nodejs -y 4. Run `ghost version` to get your current Ghost version 5. `ghost update [version] --force` to force Ghost to reinstall your current version of Ghost, to trigger a re-install of dependencies. It’s not recommended to update Node and Ghost at the same time. Reinstalling the current version of Ghost ensures that Node is updated correctly first so that you can be sure that Ghost will update smoothly. Common Errors ------------- **Script deprecation warning**: Node changed its distribution method, which requires adjusting configurations in order to upgrade. [Learn more](https://github.com/nodesource/distributions?tab=readme-ov-file#faq) . If you attempt to install Ghost with an unsupported version of Node.js, you may encounter the error **ERROR: Unsupported version of Node** and/or **Exit status 231**. To resolve this, you must install a compatible version of Node.js. Why follow LTS? --------------- We encourage the community to support each other, but more often than not it falls to us to ensure our users have a good experience. Ghost is a tiny team with ambitious plans and we need to focus on delivering a useful product. By following LTS we can focus our efforts on meaningful work and keep our time spent on node versioning to a minimum. It also allows us to delay the support impact until later and gives us a chance to grow our team to cope with the support in the meantime. If you want to run Ghost on the latest version of Node, you can, however, we can’t offer support for any issues. Node compatibility matrix ========================= | Released | Version | Node support | Notes | | --- | --- | --- | --- | | 2017/12/05 | ≥1.18.3 | ^4.5.0, ^6.9.0 or ^8.9.0 | | | 2018/04/17 | ≥1.22.3 | | Bumped Ghost-CLI to ^1.7.0, which requires ^4.5.0, ^6.9.0 or ^8.9.0 | | 2018/05/04 | ≥1.22.5 | ^6.9.0 or ^8.9.0 | Removed Node 4 | | 2018/07/24 | 1.25.0 | | Final 1.x migrations | | 2019/12/18 | 1.26.2 | | Final 1.x version | | 2018/08/16 | 2.0.0 | | Bumped Ghost-CLI to ^1.9.0, which requires ^6.9.0 or ^8.9.0 | | 2018/10/30 | ≥2.4.0 | ^6.9.0, ^8.9.0 or ^10.12.0 | Added Node 10 | | 2018/11/07 | ≥2.5.0 | ^6.9.0, ^8.9.0 or ^10.13.0 | Bumped Node 10 | | 2019/06/04 | ≥2.23.2 | ^8.9.0 or ^10.13.0 | Removed Node 6 | | 2019/07/16 | ≥2.25.7 | ^8.10.0 or ^10.13.0 | Bumped Node 8 | | 2019/10/14 | 2.37.0 | | Final 2.x migrations | | 2021/01/29 | 2.38.3 | | Final 2.x version | | 2019/10/22 | 3.0.0 | ^8.16.0, ^10.13.0 or ^12.10.0 | Added Node 12 and bumped Ghost-CLI to ^1.12.0, which requires ^8.16.0, ^10.13.0 or ^12.10.0 | | 2020/03/02 | ≥3.9.0 | ^10.13.0 or ^12.10.0 | Removed Node 8 | | 2020/11/03 | ≥3.37.0 | ^10.13.0, ^12.10.0 or ^14.15.0 | Added Node 14 | | 2021/01/26 | 3.41.0 | | Final 3.x migrations | | 2022/01/22 | 3.42.9 | | Final 3.x version | | 2021/03/15 | 4.0.0 | | Bumped Ghost-CLI to ^1.16.0, which requires ^10.13.0, ^12.10.0 or ^14.15.0 | | 2021/05/11 | ≥4.5.0 | ^12.22.1 or ^14.16.1 | Removed Node 10 and bumped Ghost-CLI to ^1.17.0, which requires ^10.13.0, ^12.10.0 or ^14.15.0 | | 2021/10/29 | ≥4.21.0 | ^12.22.1, ^14.17.0 or ^16.13.0 | Added Node 16 | | 2022/04/22 | ≥4.45.0 | ^14.17.0 or ^16.13.0 | Removed Node 12 | | 2023/01/05 | ≥5.27.0 | ^14.18.0 or ^16.13.0 or ^18.12.1 | Added Node 18 and bumped Node 14 minimum | | 2023/05/05 | ≥5.47.0 | ^16.13.0 or ^18.12.1 | Removed Node 14 | | 2023/10/27 | ≥5.71.0 | ^18.12.1 | Removed Node 16 | | 2024/04/19 | ≥5.82.3 | ^18.12.1 \| ^20.11.1 | Added Node 20 | | 2025/02/21 | ≥5.110.0 | ^18.12.1 \| ^20.11.1 \| ^22.13.1 | Added Node 22 and bumped Ghost-CLI to ^1.27.0 | Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to install Ghost locally on Mac, PC or Linux Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Fast-track local install for getting Ghost running on your computer or setup for doing theme development Overview -------- Running Ghost locally is the easiest way to get your own copy of the software running and be able to do some local development with it. By the end of this guide you will have completed a local Ghost install that runs in development mode using SQLite3. This install is **not** suitable for [production use](/docs/install/ubuntu/) or [contributing](/docs/install/source/) to core. * * * Prerequisites ------------- To install Ghost locally you will need the following: * A computer running MacOS, Windows or Linux * A [supported version](/docs/faq/node-versions/) of [Node.js](https://nodejs.org) * Either [yarn](https://yarnpkg.com/en/docs/install#alternatives-tab) or [npm](https://www.npmjs.com/get-npm) to manage packages * A clean, empty directory on your machine * * * Install Ghost-CLI ----------------- [Ghost-CLI](/docs/ghost-cli/) is a commandline tool to help you get Ghost installed and configured for use, quickly and easily. The npm module can be installed with `npm` or `yarn` on a local machine, depending on your preference. npm install ghost-cli@latest -g Once installed, you can always run `ghost help` to see a list of available commands. * * * Install Ghost ------------- In your terminal, `cd` into an empty directory and run the install command: ghost install local Once the install is finished you’ll be able to access your new site on `http://localhost:2368` and `http://localhost:2368/ghost` to access Ghost Admin ✨ **That’s it! You’re done.** * Your publication is setup in `development` mode with less caching * The SQLite3 database is auto-setup and located in `/content/data/` * Logs only go to `stdout` * * * Starting & Stopping ------------------- Ghost runs in a separate background process and remains running until you stop it or restart your computer. So you may find these commands useful for taming it: * `ghost stop` to stop Ghost * `ghost start` to start Ghost * `ghost log` views logs * `ghost ls` to list all running Ghost blogs Run `ghost help` for a list of available commands, or explore the full [Ghost-CLI documentation](/docs/ghost-cli/) . #### Troubleshooting For troubleshooting and errors, try searching this documentation and [FAQ section](/docs/faq/) to find information about common error messages. * * * Developing Themes ----------------- To work on a [Ghost Handlebars Theme](/docs/themes/) locally, your custom theme should always be placed in the top-level `/content/themes/` directory. ### Live reloading All edits made to Ghost theme files will automatically reload. If you add any **new** files to your theme during development, you’ll need to restart Ghost to see the changes take effect. ### Validating with GScan GScan is a tool that validates Ghost themes for compatibility with the latest versions of Ghost. Ghost automatically runs this tool when a theme is uploaded or activated. For development purposes, your can also run these checks yourself by locally installing it. # Install gscan globally npm install gscan -g # Scan a theme directory for compatibility gscan /path/to/ghost/content/themes/casper # Scan a theme zip file for compatibility gscan -z /path/to/downloads/theme.zip GScan can also be accessed at [gscan.ghost.org](https://gscan.ghost.org/) , where you can sign up for the latest updates as a Ghost theme developer. What’s next ----------- You’ve completed a local Ghost install — congrats! You can now put Ghost through its paces and see what it’s all about, or jump right into developing a custom Ghost theme. When you’re ready ship your site to production, [follow one of these guides](/docs/install/) . For more information about theme development read the [Handlebars theme documentation](/docs/themes/) and check out the [tutorials](/tutorials/) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Docs Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) This guide is for installing a local development copy of Ghost from source, primarily for the purpose of modifying Ghost core. Prerequisites ------------- Before getting started, you’ll need these global packages to be installed: * **A [supported version](/docs/faq/node-versions/) of [Node.js](https://nodejs.org) ** - Ideally installed via [nvm](https://github.com/creationix/nvm#install-script) * **[Yarn](https://yarnpkg.com/en/docs/install#alternatives-tab) ** - to manage all the packages * **Docker (ie. [Docker Desktop](https://www.docker.com/products/docker-desktop/) )** - to run the MySQL database and other services * * * Create GitHub fork ------------------ First you’ll need to make a fork of the [Ghost](https://github.com/tryghost/ghost) repository. Click on the fork button right at the top, wait for a copy to be created over on your personal GitHub account, and you should be all set! ![Fork](/images/docs/setup/fork.gif) * * * Configure repository -------------------- The main Ghost repository is a monorepo containing the full Ghost code, including the Admin client and default theme which will also be automatically set up # First clone Ghost with submodules and make it your working dir git clone --recurse-submodules git@github.com:TryGhost/Ghost && cd Ghost #### Properly rename your references If you’re part of the Ghost core team, you don’t need to do this, as you can push straight to `TryGhost/Ghost`. # Rename origin to upstream git remote rename origin upstream # Add your fork as an origin, editing in ! git remote add origin git@github.com:/Ghost.git * * * Run setup & installation ------------------------ # Only ever run this once yarn setup The `setup` task will install dependencies, initialise the database, set up git hooks, and initialise submodules. * * * Start Ghost ----------- # Run Ghost in development mode yarn dev **Ghost is now running at** http://localhost:2368/ - **Ghost Admin** lives at http://localhost:2368/ghost/ * * * Stay up to date --------------- When your working copies become out of date due to upstream changes, this is the command that brings you back up to the latest `main` # Update EVERYTHING yarn main That’s it, you’re done with the install! The rest of this guide is about working with your new development copy of Ghost. * * * Dev Commands ------------ When running locally there are a number development utility commands which come in handy for running tests, building packages, and other helpful tasks. ### Running Ghost The most commonly used commands for running the core codebase locally # Default way of running Ghost in development mode # Builds admin files on start & then watches for changes yarn dev # Ignores admin changes yarn dev:ghost # Ignores server changes yarn dev:admin # Run Ghost, Admin and the Portal dev server yarn dev --portal ### Database tools Ghost uses its own tool called `knex-migrator` to manage database migrations. # Wipe the database yarn knex-migrator reset # Populate a fresh database yarn knex-migrator init ### Building Ghost from source From the top-level directory of the monorepo, run yarn archive This will produce a tarball archive called `ghost-.tgz`, which can be installed with Ghost-CLI’s [`--archive` flag](/docs/ghost-cli/#ghost-install) . ### Server Tests Tests run with SQLite. To use MySQL, prepend commands with `NODE_ENV=testing-mysql` # Run unit tests yarn test:unit # Run acceptance tests yarn test:acceptance # Run regression tests yarn test:regression # Run a single test yarn test:single path/to/test.js # Run a folder of tests yarn test:single test/unit/helpers # Run all tests yarn test:all # Make sure your code doesn't suck yarn lint ### Client Tests Client tests should always be run inside the `ghost/admin` directory. Any time you have `yarn dev` running the client tests will be available at `http://localhost:4200/tests` # Run all tests in Chrome + Firefox ember test # Run all tests, leave results open, and watch for changes ember test --server # Run tests where `describe()` or `it()` matches supplied argument # Note: Case sensitive ember test -f 'gh-my-component' # Run all tests in Chrome only ember test --launch=chrome # Most useful test comment for continuous local development # Targets specific test of area being worked on # Only using Chrome to keep resource usage minimal ember test -s -f 'Acceptance: Settings - General' --launch=chrome * * * Troubleshooting --------------- Some common Ghost development problems and their solutions **ERROR: (EADDRINUSE) Cannot start Ghost** This error means that Ghost is already running, and you need to stop it. **ERROR: ENOENT** This error means that the mentioned file doesn’t exist. **ERROR Error: Cannot find module** Install did not complete. Run `yarn fix`. **Error: Cannot find module ‘./build/default/DTraceProviderBindings’** You switched node versions. Run `yarn fix`. **ENOENT: no such file or directory, stat ‘path/to/favicon.ico’ at Error (native)** Your admin client has not been built. Run `yarn dev`. **TypeError: Cannot read property ’tagName’ of undefined** You can’t run `ember test` at the same time as `yarn dev`. Wait for tests to finish before continuing and wait for the “Build successful” message before loading admin. **yarn.lock conflicts** When rebasing a feature branch it’s possible you’ll get conflicts on `yarn.lock` because there were dependency changes in both `main` and ``. 1. Note what dependencies have changed in `package.json` (Eg. `dev-1` was added and dev dep `dev-2` was removed) 2. `git reset HEAD package.json yarn.lock` - unstages the files 3. `git checkout -- package.json yarn.lock` - removes local changes 4. `yarn add dev-1 -D` - re-adds the dependency and updates yarn.lock 5. `yarn remove dev-2` - removes the dependency and updates yarn.lock 6. `git add package.json yarn.lock` - re-stage the changes 7. `git rebase --continue` - continue with the rebase It’s always more reliable to let `yarn` auto-generate the lockfile rather than trying to manually merge potentially incompatible changes. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Updates: How to update to the latest major version Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Learn how to update your self-hosted Ghost install to the latest major version. Before updating to a new major version, you must update your site to the latest minor version, e.g if you are on 3.23.0 you need to be on 3.42.9 before updating to v5. You also need to make sure that your setup meets all the latest requirements. This guide will take you through everything step-by-step. **Updates are recommended for sites that are:** * Running Ghost version `3.0.0` or higher and are using MySQL in production * Development sites using any database > Make sure you can login to Ghost Admin before starting this process. Switch user ----------- Switch to the user you used to setup your Ghost install for running Ghost commands: `sudo -i -u ` Update Node ----------- Run `node -v` to check your node version. If less than 14, update node: * `curl -fsSL [https://deb.nodesource.com/setup_14.x](https://deb.nodesource.com/setup_14.x) | sudo -E bash -` * `sudo apt-get install -y nodejs` Update Ghost CLI ---------------- Ghost-CLI is an npm module that can be updated using npm: `sudo npm install -g ghost-cli@latest` Update to the latest minor version ---------------------------------- Before updating to a new major version, you must update your site to the latest minor version. First, perform a preliminary backup of your site: `ghost backup` Then, update to the latest minor version: * On Ghost 3.0 run `ghost update v3` * On Ghost 4.0 run `ghost update v4` If Ghost is not already running, run `ghost start`, then login to Ghost Admin and check this update has worked before continuing. Make a full backup ------------------ When performing manual updates it’s a good idea to make frequent backups, so if anything goes wrong, you’ll still have all your data. Once you’re running the latest minor version, make a full backup using the following command to generate a download of your site’s data: `ghost backup` This creates a backup zip file of everything you need, including: * Your content in JSON format * A full member CSV export * All themes that have been installed including your current active theme * Images, files, and media (video and audio) * A copy of `routes.yaml` and `redirects.yaml` or `redirects.json` Read more about how to [manually download your site data](/docs/faq/manual-backup/) . Check MySQL version ------------------- MySQL 8 is the only supported database in production. Use the following commands to check your database version: `ghost config database.client` This should output `sqlite3` or `mysql`. If it says mysql, run `mysql --version` to find the details of your mysql install. If you’re already running MySQL 8, proceed to the next steps. Otherwise, [read more](/docs/faq/supported-databases/) about how to update or migrate your database if you’re using any other version. Check for theme update requirements ----------------------------------- To ensure your publication’s front-end doesn’t break, you may need to make some changes to your theme. Ghost CLI will output a report with any theme incompatibility errors and recommendations, or you can upload your theme directly to the **[GScan](https://gscan.ghost.org/) ** theme testing tool. Update to the latest major version ---------------------------------- > Before updating to a new major version, review [breaking changes](/docs/changes/) > that might affect you. Once you’re running the latest minor version, it’s time to update to the latest major version. Finally, you can now run the update command again to upgrade to the latest major version: `ghost update` * * * Troubleshooting --------------- The most common reason for an update failure is running out of memory, so make sure you have enough RAM or swap configured in advance. Find your Ghost folder ---------------------- Run the `ghost ls` command anywhere on your server to find the details of your Ghost install. ### Download your backup If you need to move your backup between machines, run the `scp` command on the machine you want to move the file _to:_ `scp user@123.456.789.123:/var/www/ghost/backup-from[...].zip ~/Desktop` ### Force update If an update fails you can start by forcing a retry to attempt the upgrade a second time. `ghost update --force` ### Rollback If something goes wrong, you can always revert to the previous stable version of Ghost. `ghost update --rollback` If you’re still having trouble, start by searching the **[Ghost forum](https://forum.ghost.org/) ** to see if your issue has come up before. ### Downgrade Node If you need to downgrade Node, these are the steps: sudo apt-get remove -y nodejs curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - sudo apt-get install -y nodejs Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Build A Custom Static Site With Headless Ghost + Gatsby Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Build a custom front-end for your Ghost site with the power of Gatsby.js ![Diagram of content API to Gatsby](/images/docs/jamstack/admin-api-gatsby-diagram_hu088f0fec0d83414e79d90f8ae3457e19_21185_1000x0_resize_q100_h2_box_3.webp) Gatsby Starter Ghost -------------------- One of the best ways to start a new Gatsby site is with a Gatsby Starter, and in this case, it’s no different. #### Prerequisites To use Gatsby Starters, and indeed Gatsby itself, the [Gatsby CLI](https://www.gatsbyjs.org/docs/quick-start/) tool is required. Additionally, a [Ghost account](/pricing/) is needed to source content and get site related credentials. #### Getting started To begin, generate a new project using the [Gatsby Starter Ghost](https://github.com/TryGhost/gatsby-starter-ghost) template with the following CLI command: gatsby new my-gatsby-site https://github.com/TryGhost/gatsby-starter-ghost.git Navigate into the newly created project and use either npm install or yarn to install the dependencies. The Ghost team prefer to use [Yarn](https://yarnpkg.com/en/docs/install#mac-stable) . Before customising and developing in this new Gatsby site, it’s wise to give it a test run to ensure everything is installed correctly. Use the following command to run the project: gatsby develop Then navigate to `http://localhost:8000/` in a browser and view the newly created Gatsby site. ![Gatsby demo screenshot](/images/docs/jamstack/gatsby-demo-screenshot_huf503a446e74501027d0049b3b70cf420_364260_1280x0_resize_q100_h2_box_3.webp) Making it your own ------------------ So, you’ve set up a Gatsby site, but it’s not showing the right content. This is where content sourcing comes into play. Gatsby uses [GraphQL](https://graphql.org/) as a method of pulling content from a number of APIs, including Ghost. Sourcing content from Ghost in the Gatsby Starter Ghost template is made possible with the [Gatsby Source Ghost](https://github.com/TryGhost/gatsby-source-ghost) plugin. Configuring the plugin can be done within the template files. Within the project, navigate to and open the file named `.ghost.json`, which is found at root level: // .ghost.json {   "development": {     "apiUrl": "https://gatsby.ghost.io",     "contentApiKey": "9cc5c67c358edfdd81455149d0"   },   "production": {     "apiUrl": "https://gatsby.ghost.io",     "contentApiKey": "9cc5c67c358edfdd81455149d0"   } } This json file is set up to make environment variables a bit easier to control and edit. Change the apiUrl value to the URL of the site. For Ghost(Pro) customers, this is the Ghost URL ending in .ghost.io, and for people using the self-hosted version of Ghost, it’s the same URL used to view the admin panel. In most cases, it’s best to change both the development and production to the same site details. Use the respective environment objects when using production and development content; this is ideal if you’re working with clients and test content. After saving these changes, restart the local server. Using [Netlify](https://www.netlify.com/) to host your site? If so, the `netlify.toml` file that comes with the starter template provides the deployment configuration straight out of the box. Next steps ---------- [The official Gatsby docs](https://www.gatsbyjs.org/docs/gatsby-project-structure/) is a great place to learn more about how typical Gatsby projects are structured and how it can be extended. Gaining a greater understanding of how data and content can be sourced from the Ghost API with GraphQL will help with extending aforementioned starter project for more specific use cases. There’s also a guide for setting up a new static site, such as Gatsby, [with the hosting platform Netlify](/integrations/netlify/) . For community led support about linking and building a Ghost site with Gatsby, [visit the forum](https://forum.ghost.org/c/themes/) . As with all content sources for Gatsby, content is fed in by [GraphQL](https://www.gatsbyjs.org/tutorial/part-four/) , and it’s no different with Ghost. The official [Gatsby Source Ghost](https://github.com/TryGhost/gatsby-source-ghost) plugin allows you to pull content from your existing Ghost site. Getting started --------------- Installing the plugin is the same as any other Gatsby plugin. Use your CLI tool of choice to navigate to your Gatsby project and a package manager to install it: # yarn users yarn add gatsby-source-ghost # npm users npm install --save gatsby-source-ghost After that, the next step is to get the API URL and Content API Key of the Ghost site. The API URL is domain used to access the Ghost Admin. For Ghost(Pro) customers, this is the `.ghost.io`, for example: `mysite.ghost.io`. For self-hosted versions of Ghost, use the admin panel access URL and ensure that the self-hosted version is served over a https connection. The Content API Key can be found on the Integrations screen of the Ghost Admin. Open the `gatsby-config.js` file and add the following to the `plugins` section: // gatsby-config.js { resolve: `gatsby-source-ghost`, options: { apiUrl: `https://.ghost.io`, contentApiKey: `` } } Restart the local server to apply these configuration changes. Querying Graph with GraphQL --------------------------- The Ghost API provides 5 types of nodes: * Post * Page * Author * Tag * Settings These nodes match with the endpoints shown in the [API endpoints documentation](/docs/content-api/#endpoints) . Querying these node with GraphQL can be done like so: { allGhostPost(sort: { order: DESC, fields: [published_at] }) { edges { node { id slug title html published_at } } } } The above example is retrieving all posts in descending order of the ‘published at’ field. The posts will each come back with an id, slug, title, the content (html) and the ‘published at’ date. Next steps ---------- GraphQL is a very powerful tool to query the Ghost API with. This is why we’ve documented a few recipes that will get you started. To learn more about the plugin itself, check out the [documentation within the repo on GitHub](https://github.com/TryGhost/gatsby-source-ghost#how-to-query) . There’s also plenty of documentation on what the Ghost API has to offer when making queries. To learn more about GraphQL as a language, head over to the [official GraphQL docs](https://graphql.org/learn/queries/) . Use-cases --------- There are many additional aspects to switching from a typical Ghost front-end to a standalone API driven front-end like Gatsby. The following sections explain some slightly ‘grey area’ topics that have been commonly asked or may be of use when making this transition. Switching over -------------- Switching to a new front-end means handling the old front-end in a different way.  One option is to make the old pages canonical, meaning that these pages will remain online, but will reference the new counterparts on the API driven site. Check out the documentation on [using canonical URLs in Ghost](/help/publishing-options/#add-custom-canonical-urls) . ![Screenshot of the private option in the Ghost admin](/images/docs/jamstack/admin-private-option_hub2336ad8c44cf39926a93b72f74de9cd_10436_800x0_resize_q100_h2_box_3.webp) Another way is to turn off the old site entirely and begin directing people to the new site. Ghosts’ front-end can be hidden using the ‘Private Mode’ found in the Ghost Admin under General Settings. Generating a sitemap -------------------- Providing a well made sitemap for search indexing bots is one of the most important aspects of good SEO. However, creating and maintaining a series of complex ‘for loops’ can be a costly exercise. ![Screenshots of an XML sitemap before and after the plugin has been applied](/images/docs/jamstack/xml-sitemap-before-and-after_huaa7504f9f8a1eda4d36a79fb085bcdc6_679990_2068x0_resize_q100_h2_box_3.webp) The Ghost team have provided an open source plugin for Gatsby to construct an ideal format for generated sitemap XML pages, called [Gatsby Advanced Sitemap plugin](https://github.com/TryGhost/gatsby-plugin-advanced-sitemap) . By default, the plugin will generate a single sitemap, but it can be [configured with GraphQL](https://github.com/TryGhost/gatsby-plugin-advanced-sitemap#options) to hook into various data points. Further information can be found in the [sitemap plugin documentation](https://github.com/TryGhost/gatsby-plugin-advanced-sitemap#gatsby-plugin-advanced-sitemap) . The plugin doesn’t just work with Ghost - it’s compatible with an assortment of APIs and content sources. To learn more about using GraphQL and the Ghost API for plugins, such as the Gatsby sitemap plugin, check out our GraphQL Recipes for Ghost. Using Gatsby plugins with Ghost content --------------------------------------- With the ever expanding list of plugins available for Gatsby, it’s hard to understand which plugins are needed to make a high quality and well functioning site running on the Ghost API. [Gatsby Source Filesystem](https://www.gatsbyjs.org/packages/gatsby-source-filesystem/) is a plugin for creating additional directories inside a Gatsby site. This is ideal for storing static files (e.g. error pages), site-wide images, such as logos, and site configuration files like robots.txt. [Gatsby React Helmet plugin](https://www.gatsbyjs.org/packages/gatsby-plugin-react-helmet/) is very useful for constructing metadata in the head of any rendered page. The plugin requires minimum configuration, but can be modified to suit the need. Further reading --------------- There is plenty of reference material and resources on the [official Gatsby site](https://www.gatsbyjs.org/tutorial/) , along with a long list of [available plugins](https://www.gatsbyjs.org/plugins/) . It may also be worth understanding the underlying concepts of [static sites](https://jamstack.org/) and how they work differently to other sites. To get an even more boarder view of performant site development check out web.dev from Google, which explores many topics on creating site for the modern web. Examples -------- Here are a few common examples of using GraphQL to query the Ghost API. Gatsby uses [GraphQL](https://www.gatsbyjs.org/docs/graphql/) to retrieve content, retrieving content from the Ghost API is no different thanks to the Gatsby Source Ghost plugin. Below are some recipes to retrieve chunks of data from the API that you can use and manipulate for your own needs. More extensive learning can be found in the official [GraphQL documentation](https://graphql.org/graphql-js/passing-arguments/) . Retrieving posts ---------------- This example takes into account a limited amount of posts per page and a ‘skip’ to paginate through those pages of posts: query GhostPostQuery($limit: Int!, $skip: Int!) {   allGhostPost(       sort: { order: DESC, fields: [published_at] },       limit: $limit,       skip: $skip   ) {     edges {       node {         ...GhostPostFields       }     }   } } Filtering Posts by tag ---------------------- Filtering posts by tag is a common pattern, but can be tricky with how the query filter is formulated: {   allGhostPost(filter: {tags: {elemMatch: {slug: {eq: $slug}}}}) {     edges {       node {         slug         ...       }     }   } } Retrieving settings ------------------- The Ghost settings node is different to other nodes as it’s a single object - this can be queried like so: {   allGhostSettings {     edges {       node {         title         description         lang         ...         navigation {             label             url         }       }     }   } } More information can be found in the [Ghost API documentation](/docs/content-api/#settings) . Retrieving all tags ------------------- Getting all tags from a Ghost site could be used to produce a tag cloud or keyword list: {   allGhostTag(sort: {order: ASC, fields: name}) {       edges {           node {               slug               url               postCount           }       }   } } Further reading --------------- Many of the GraphQL queries shown above are used within the [Gatsby Starter Ghost](https://github.com/tryghost/gatsby-starter-ghost) template. With a better understanding of how to use queries, customising the starter will become more straightforward. Additionally, the [Gatsby Source Ghost plugin](https://github.com/TryGhost/gatsby-source-ghost) allows the use of these queries in any existing Gatsby project you may be working on. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to install Ghost on Digital Ocean - Official guide Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) A full guide for installing, configuring and running Ghost on a Digital Ocean Droplet VPS, for use in production environments. Overview -------- Digital Ocean the official hosting partner of the Ghost open source project, and our favourite provider! Our teams have worked together to make installing Ghost on Digital Ocean VPS droplets as easy as possible for developers, with officially supported 1-click marketplace apps. Prerequisites ------------- For this install there are three things which you need to have ready up-front: 1. A DigitalOcean account ([This signup link](https://m.do.co/c/9ff29836d717) will give you $100 free credit) 2. Your SSH key added to the account. [Follow these instructions](https://www.digitalocean.com/docs/droplets/how-to/add-ssh-keys/to-account/) 3. A domain name to point at your new site (Mandatory for SSL configuration during install) * * * Setting up the marketplace app ------------------------------ Head over to [the official Ghost app in the Digital Ocean marketplace](https://marketplace.digitalocean.com/apps/ghost) , and click on the “Create Ghost Droplet” button. ![Select a size](/images/docs/install/digitalocean-1_huf7cfbbf5bf09b89b2ece9aa193086014_364320_2648x0_resize_q100_h2_box_3.webp) For most Ghost installs, the $5 base option is sufficient to run the app. If you’re running a high traffic site or one with lots of members, then you may want to choose an option with more resources. ![Select a size](/images/docs/install/digitalocean-2_hu99e10db7fda0845d639fea0ea6962c3c_57813_2170x0_resize_q100_h2_box_3.webp) The rest of the options for the Droplet can be selected based on what you prefer. Make sure you only create **one** Droplet, Ghost runs on a single process and cannot be sharded across multiple machines. When you’re done, click the “Create Droplet” button at the bottom of the screen. Server Setup ------------ Once the Droplet has been created you will have an IP address to log into. Open your terminal and login to your new server as the root user, with SSH: # Login via SSH ssh root@your_server_ip At the first login Ghost-CLI will automatically perform an update check for all available packages, and make sure that the environment is ready for use. When finished, you’ll see a prompt to let you know what’s next: Ghost will prompt you for two details: 1. Your domain - Add an A Record -> xxx.xxx.xxx.xxx & ensure the DNS has fully propagated - Or alternatively enter http://xxx.xxx.xxx.xxx 2. Your email address (only used for SSL) Press enter when you're ready to get started! Configuring your domain ----------------------- It’s best to set up your domain and DNS at this point so that SSL certificates can be automatically provisioned during the install process. Create an **A-Record** from your domain, pointing at your server’s IP address, then press Enter in terminal when you’re ready. Setting up Ghost ---------------- Ghost-CLI will now install and configure Ghost for you, and along the way it will prompt you with some questions about how you’d like things to be set up: ##### Blog URL Enter the exact URL (the domain you pointed at your droplet’s IP address) that your publication will use, and include the protocol for HTTP or HTTPS. For example, `https://example.com`. If you use HTTPS, Ghost-CLI will offer to set up SSL for you. Your new site ------------- Once the install is finished you’ll be able to access your new site on `https://yourdomain.com` and `https://yourdomain.com/ghost` to access Ghost Admin ✨ **That’s it! You’re done.** * * * #### Future maintenance Once Ghost is properly set up it’s important to keep it properly maintained and up to date. Fortunately, this is relatively easy to do using Ghost-CLI. Run `ghost help` for a list of available commands, or explore the full [Ghost-CLI documentation](/docs/ghost-cli/) . * * * #### What to do if the install fails If an install goes horribly wrong, use `ghost uninstall` to remove it and try again. This is preferable to deleting the folder to ensure no artifacts are left behind. If an install is interrupted or the connection lost, use `ghost setup` to restart the configuration process. If the install succeeds, but starting Ghost fails, you can try starting ghost manually with `ghost start`. For troubleshooting and errors, use the site search and [FAQ section](/docs/faq/) to find information about common error messages. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Substack to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Substack and import your content to Ghost with this guide If you're a Ghost(Pro) customer, our team may be able to help you migrate your content and subscribers. Learn more about our [Concierge service](/concierge/) . Exporting your subscribers -------------------------- To get started, export **All Subscribers** in CSV format. This exports your entire Subscriber list from Substack: ![substack export](/images/docs/migration/export-subscribers_hu9b5a04e10eb52f416de1a1f6ddfeb3d7_16147_1942x0_resize_q100_h2_box_3.webp) If you’re also migrating paid subscribers, you’ll need to export the Stripe ID’s of your customers by exporting the **Paid Subscribers** list, which downloads a separate CSV with this information included. Now you’ll have all of the the email subscriber and customer data required to migrate from Substack to Ghost. Import subscribers to Ghost --------------------------- We recommend preparing the data in your CSV using our guide on [importing members in Ghost](/help/import-members/) . Alternatively, [we can do this work for you](/concierge/) with an annual payment on the Ghost(Pro) Creator, Team, or Business plans. Once you’re happy that you subscriber data is complete, under the Ghost Admin members settings, select the import option from the settings menu. ![import members](/images/docs/migration/import-members-1_huc3d26abd3bec140dac4d1e5fd61f2b53_17353_1000x0_resize_q100_h2_box_3.webp) Upload your CSV file to Ghost, and map each of the fields contained in your CSV to the corresponding fields in Ghost. The **email** field is required in order to create members in Ghost, while the other fields are optional. ### Importing paid members To import paid members with an existing Stripe subscription, you must import their **Stripe customer ID**. ![import members](/images/docs/migration/import-members-2_hu4666f031efd860d74fa4f40b2a587fd0_130604_1000x0_resize_q100_h2_box_3.webp) Once the import has completed, all your subscribers will be migrated to Ghost. ### Importing subscribers with a `comp` or `gift` plan If you’ve provided any of your subscribers with a free or gifted paid access to premium content in Substack, you can also give them free access to paid content in Ghost by importing their email address with the Complimentary Plan column flagged as `true` in your [CSV import](/help/import-members/) . This provides these members with unlimited free access to premium content on your Ghost publication with an [access level](/help/protected-content/) of `paid-members only`. If you’d like your members complimentary access to expire after a specific date, the easiest way to do this is to edit the complimentary subscription directly in Stripe and schedule the date the subscription should be cancelled. ![import members](/images/docs/migration/update-comp-plan.gif) Removing Substack fees ---------------------- Because of how Stripe works, disconnecting will **not** prevent Substack from continuing to take a 10% commission on all existing subscriptions. Substack have an official policy that you can contact them before leaving to have these fees removed. Ask their support team to remove fees, unlink your Stripe account, and leave subscriptions as-is in Stripe. Once fees have been removed, and you have completed your migration, you should ensure Substack is disconnected from Stripe to prevent subscriptions from getting out-of-sync in future. You can do this by going to [https://dashboard.stripe.com/account/applications](https://dashboard.stripe.com/account/applications) — and clicking on **Revoke Access**. ![disconnect substack from stripe](/images/docs/migration/disconnect-application_hu5415c58dc1ab28674219d7e93aa41f5c_57584_1817x0_resize_q100_h2_box_3.webp) Migrating Content ----------------- Developers can migrate content from Substack to Ghost using our [migration CLI tools](https://github.com/TryGhost/migrate/tree/main/packages/mg-substack) . You will first need to [export your content](https://support.substack.com/hc/en-us/articles/360037466012-How-do-I-export-my-posts-) from Substack. This will include a file called `posts.csv` which includes meta data for each post, and a directory full of HTML files which contains the post content. First, make sure the CLI is installed. # Install CLI npm install --global @tryghost/migrate # Verify it's installed migrate To run a basic migration with the default commands: # Basic migration migrate substack --pathToZip export.zip --url https://mysite.substack.com In the above example: * Changes subscribe links to open [Portal](/docs/themes/members/) * Changes comments links to link to `#ghost-comments-root` * Excludes thread posts * Uses the `og:image` value as the feature image * Uses the `ld+json` data to set the post authors By setting some options, # Migration with options migrate substack --pathToZip export.zip --url https://mysite.substack.com --email 'person@example.com' --drafts false --commentLink \#comments In addition to the basic example above, this more complex example: * Excludes draft posts * Sets the post author to be `person@example.com` * Changes comment links to `#comments` There are [more options](https://github.com/TryGhost/migrate/tree/main/packages/mg-substack#usage) , such as the ability to only migrate content to and from specific dates, handling of draft posts, and more. Once the CLI task has finished, it creates a new ZIP file which you can [import into Ghost](https://ghost.org/help/imports/) . ### Using custom domains If you’re using a custom domain on Substack, you’ll need to implement redirects in Ghost to prevent broken links. Substack uses `/p/` as part of the public post URL, where as Ghost uses it in the URL for post previews. This means the redirect regular expression is quite complex, but necessary so that post previews in Ghost function correctly. # redirects.yaml 301: \/p\/(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})(.*): /$1 302: This means that if a visitor or crawler goes to `https://mysite.com/p/awesome-post`, they will automatically be redirected to `https://mysite.com/awesome-post`. * * * Summary ------- Congratulations on your migration to Ghost 🙌. All that’s left to do is check over your content to ensure the migration has worked as expected. We also have a guide on [how to implement redirects](/docs/tutorials/implementing-redirects/) to make your transition smoother. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to reinstall Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Learn how to update to the latest version of Ghost by creating a new install. A full reinstall of Ghost is recommended for: * Sites running Ghost `0.x`, `1.x`, `2.x` * Sites using SQLite3 in production on any Ghost version Switch user ----------- Switch to the user you used to setup your Ghost install for running Ghost commands: `sudo -i -u ghost-mgr` Update Node ----------- Run `node -v` to check your node version. If less than 14, update node: * `curl -fsSL [https://deb.nodesource.com/setup_14.x](https://deb.nodesource.com/setup_14.x) | sudo -E bash -` * `sudo apt-get install -y nodejs` Update Ghost CLI ---------------- Ghost-CLI is an npm module that can be updated using npm. `sudo npm install -g ghost-cli@latest` Update to the latest minor version ---------------------------------- Before updating to a new major version, you must update your site to the latest minor version. First, perform a preliminary backup of your site: `ghost backup` Then, update to the latest minor version using: `ghost update`. This command will inform you of a more specific command to run. Make a full backup ------------------ When performing manual updates it’s a good idea to make frequent backups, so if anything goes wrong, you’ll still have all your data. Once you’re running the latest minor version, make a full backup using the following command to generate a download of your site’s data: `ghost backup` This creates a backup zip file of everything you need, including: * Your content in JSON format * A full member CSV export * All themes that have been installed including your current active theme * Images, files, and media (video and audio) * A copy of `routes.yaml` and `redirects.yaml` or `redirects.json` Read more about how to [manually download your site data](/docs/faq/manual-backup) . Disconnect Stripe ----------------- _Skip this step if your Ghost site is not connected to Stripe._ If you have Ghost connected to a Stripe account in _Live mode,_ it needs to be disconnected in order for you to be able to reconnect on your new installation. ![Screenshot of content export in Ghost Admin](/images/docs/install/stripe-connection_hub376459ffe40b4ad66b7082f6966fac4_293097_2290x0_resize_q100_h2_box_3.webp) If you have paid members, or complimentary members with Stripe customer IDs, you need to delete these members from Ghost before disconnecting Stripe is possible. ![Screenshot of content export in Ghost Admin](/images/docs/install/filter-members_hu0f4d0735bb9ab23a49c8adf254de8a98_18345_2098x0_resize_q100_h2_box_3.webp) Filter your members list by member status to get a full list of members with paid and complimentary subscriptions, or delete your full members list (which has already been backed up) from the Member dashboard in Ghost Admin. > When deleting members with a Stripe subscription from Ghost, the subscriptions in Stripe are not affected, unless you explicitly opt to cancel them. Do not cancel the subscriptions. When all members with subscriptions are removed from Ghost, you can successfully disconnect Stripe. Once this is done, log into Stripe and delete any [webhooks](https://dashboard.stripe.com/webhooks) related to the old connection: ![Screenshot of content export in Ghost Admin](/images/docs/install/stripe-webhooks_hu7a110a240dae732aa17e84a1b4005ae4_43075_1878x0_resize_q100_h2_box_3.webp) Install Ghost ------------- Once all your data is backed up, it’s time to spin up a fresh install of Ghost to migrate your publication over to. Follow the detailed [install guides](/docs/install/) to create a new install of Ghost. When your install is complete, follow the steps to setup your new site. Import your backup data ----------------------- Once you have installed and setup a new site, it’s time to migrate your data. If you used `ghost backup` to generate a backup zip, these are the steps to restore your data. If you did a manual backup, refer to the [manual backup guide](/docs/faq/manual-backup) . 1. Starting in your ghost folder, `unzip` the backup into the `content` folder: `sudo unzip /path/to/backup-from-[backup-name].zip -d content` 2. Make sure the files have the right permissions: `sudo chown -R ghost:ghost content` 3. Restart Ghost: `ghost restart` 4. Import your content: `ghost import content/data/content-from[backup-name].json` - _This requires your username and password, and can also be done on the labs page in Ghost Admin._ Reconnect Stripe ---------------- _Skip this step if you’re not using Stripe for paid subscriptions._ To import paid members, Ghost needs to be connected to Stripe in Live mode _before_ you import your members. Make sure to connect Ghost to **the same Stripe account** you were using on your old installation - learn more about how to connect a Stripe account [in this guide](/help/setup-members/#connect-a-stripe-account) . Import members -------------- With Stripe connected, you can now import your members CSV file. You’ll receive an email notification when the import process has completed. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Content API JavaScript Client Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Ghost provides a flexible promise-based JavaScript library for accessing the Content API. The library can be used in any JavaScript project, client or server side and abstracts away all the pain points of working with API data. Working Example --------------- const api = new GhostContentAPI({ url: 'https://demo.ghost.io', key: '22444f78447824223cefc48062', version: "v5.0" }); // fetch 5 posts, including related tags and authors api.posts .browse({limit: 5, include: 'tags,authors'}) .then((posts) => { posts.forEach((post) => { console.log(post.title); }); }) .catch((err) => { console.error(err); }); Authentication -------------- The client requires the host address of your Ghost API and a Content API key in order to authenticate. The version string is optional, and indicates the minimum version of Ghost your integration can work with. The Content API URL and key can be obtained by creating a new `Custom Integration` under the **Integrations** screen in Ghost Admin. ![Get a Ghost Content API key](/images/docs/apikey_huc23d3a1fbe859434094a9db94f574d9a_265920_2920x0_resize_q100_h2_box_3.webp) * `url` - API domain, must not end in a trailing slash. * `key` - hex string copied from the “Integrations” screen in Ghost Admin * `version` - should be set to ‘v3’ See the documentation on [Content API authentication](/docs/content-api/#authentication) for more explanation. Endpoints --------- All endpoints & parameters provided by the [Content API](/docs/content-api/) are supported. // Browsing posts returns Promise([Post...]); // The resolved array will have a meta property api.posts.browse({limit: 2, include: 'tags,authors'}); api.posts.browse(); // Reading posts returns Promise(Post); api.posts.read({id: 'abcd1234'}); api.posts.read({slug: 'something'}, {formats: ['html', 'plaintext']}); // Browsing authors returns Promise([Author...]) // The resolved array will have a meta property api.authors.browse({page: 2}); api.authors.browse(); // Reading authors returns Promise(Author); api.authors.read({id: 'abcd1234'}); api.authors.read({slug: 'something'}, {include: 'count.posts'}); // include can be array for any of these // Browsing tags returns Promise([Tag...]) // The resolved array will have a meta property api.tags.browse({order: 'slug ASC'}); api.tags.browse(); // Reading tags returns Promise(Tag); api.tags.read({id: 'abcd1234'}); api.tags.read({slug: 'something'}, {include: 'count.posts'}); // Browsing pages returns Promise([Page...]) // The resolved array will have a meta property api.pages.browse({limit: 2}); api.pages.browse(); // Reading pages returns Promise(Page); api.pages.read({id: 'abcd1234'}); api.pages.read({slug: 'something'}, {fields: ['title']}); // Browsing settings returns Promise(Settings...) // The resolved object has each setting as a key value pair api.settings.browse(); For all resources except settings, the `browse()` method will return an array of objects, and the `read()` method will return a single object. The `settings.browse()` endpoint always returns a single object with all the available key-value pairs. See the documentation on [Content API resources](/docs/content-api/#resources) for a full description of the response for each resource. Installation ------------ `yarn add @tryghost/content-api` `npm install @tryghost/content-api` You can also use the standalone UMD build: `https://unpkg.com/@tryghost/content-api@{version}/umd/content-api.min.js` ### Usage ES modules: import GhostContentAPI from '@tryghost/content-api' Node.js: const GhostContentAPI = require('@tryghost/content-api'); In the browser: Get the [latest version](https://unpkg.com/@tryghost/content-api) from [unpkg.com](https://unpkg.com) . Filtering --------- Ghost provides the `filter` parameter to fetch your content with endless possibilities! Especially useful for retrieving posts according to their tags, authors or other properties. Ghost uses the NQL query language to create filters in a simple yet powerful string format. See the [NQL Syntax Reference](/docs/content-api/#filtering) for full details. Filters are provided to client libraries via the `filter` property of any `browse` method. api.posts.browse({filter: 'featured:true'}); Incorrectly formatted filters will result in a 400 Bad Request Error. Filters that don’t match any data will return an empty array. ### Working Example const api = new GhostContentAPI({ host: 'https://demo.ghost.io', key: '22444f78447824223cefc48062', version: "v5.0" }); // fetch 5 posts, including related tags and authors api.posts.browse({ filter: 'tag:fiction+tag:-fables' }) .then((posts) => { posts.forEach((post) => { console.log(post.title); }); }) .catch((err) => { console.error(err); }); ### Common Filters * `featured:true` - all resources with a field `featured` that is set to `true`. * `featured:true+feature_image:null` - looks for featured posts which don’t have a feature image set by using `+` (and). * `tag:hash-noimg` - `tag` is an alias for `tags.slug` and `hash-noimg` would be the slug for an internal tag called `#NoImg`. This filter would allow us to find any post that has this internal tag. * `tags:[photo, video, audio]` - filters posts which have any one of the listed tags, `[]` (grouping) is more efficient than using or when querying the same field. * `primary_author:my-author` - `primary_author` is an alias for the first author, allowing for filtering based on the first author. * `published_at:>'2017-06-03 23:43:12'` - looks for posts published after a date, using a date string wrapped in single quotes and the `>` operator JavaScript SDK -------------- A collection of packages for common API usecases ### Helpers * Package: `@tryghost/helpers` * Builds: CJS, ES, UMD The shared helpers are designed for performing data formatting tasks, usually when creating custom frontends. These are the underlying tools that power our [handlebars](/docs/themes/) and [gatsby](/docs/jamstack/gatsby/#custom-helpers) helpers. #### Tags Filters and outputs tags. By default, the helper will output a comma separated list of tag names, excluding any internal tags. import {tags} from '@tryghost/helpers' // Outputs e.g. Posted in: New Things, Releases, Features. posts.forEach((post) => { tags(post, {prefix: 'Posted in: ', suffix: '.'}); }); The first argument must be a post object, or any object that has a `tags` array. ##### Options The tag helper supports multiple options so that you can control exactly what is output, without having to write any logic. * `limit` {integer} - limits the number of tags to be returned * `from` {integer, default:1} - index of the tag to start iterating from * `to` {integer} - index of the last tag to iterate over * `separator` {string, default:","} - string used between each tag * `prefix` {string} - string to output before each tag * `suffix` {string} - string to output after each tag * `visibility` {string, default:“public”} - change to “all” to include internal tags * `fallback` {object} - a fallback tag to output if there are none * `fn` {function} - function to call on each tag, default returns tag.name #### Reading Time Calculates the estimated reading time based on the HTML for a post & available images. import {readingTime} from '@tryghost/helpers' // Outputs e.g. A 5 minute read. posts.forEach((post) => { readingTime(post, {minute: 'A 1 minute read.', minutes: 'A % minute read.'}); }); The first argument must be a post object, or any object that has an `html` string. If a `feature_image` is present, this is taken into account. ##### Options The output of the reading time helper can be customised through format strings. * `minute` {string, default:“1 min read”} - format for reading times <= 1 minute * `minutes` {string, default:"% min read"} - format for reading times > 1 minute #### Installation `yarn add @tryghost/helpers` `npm install @tryghost/helpers` You can also use the standalone UMD build: `https://unpkg.com/@tryghost/helpers@{version}/umd/helpers.min.js` ##### Usage ES modules: import {tags, readingTime} from '@tryghost/helpers' Node.js: const {tags, readingTime} = require('@tryghost/helpers'); In the browser: Get the [latest version](https://unpkg.com/@tryghost/helpers) from [https://unpkg.com](https://unpkg.com) . ### String * Package: `@tryghost/string` * Builds: CJS Utilities for processing strings. #### Slugify The function Ghost uses to turn a post title or tag name into a slug for use in URLs. const {slugify} = require('@tryghost/string'); const slug = slugify('你好 👋!'); // slug === "ni-hao" The first argument is the string to transform. The second argument is an optional options object. ##### Options The output can be customised by passing options * `requiredChangesOnly` {boolean, default:false} - don’t perform optional cleanup, e.g. removing extra dashes #### Installation `yarn add @tryghost/string` `npm install @tryghost/string` ##### Usage Node.js: const {slugify} = require('@tryghost/string'); Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from beehiiv to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from BeeHiiv and import your content to Ghost with this guide If you're a Ghost(Pro) customer, our team may be able to help you migrate your content and subscribers. Learn more about our [Concierge service](/concierge/) . Exporting your subscribers -------------------------- To get started, [download your full subscriber list](https://support.beehiiv.com/hc/en-us/articles/12234988536215-How-to-export-subscribers) (**Export Subscribers (Full)**) from BeeHiiv. ![start export](/images/docs/migration/beehiiv-subscriber-exports_huae1d966f695844077fd5c2e1914a81b1_25651_2148x0_resize_q100_h2_box_3.webp) ![download export](/images/docs/migration/beehiiv-subscriber-download_huff7cd490bded2324dbd2f44e962cade4_16919_2148x0_resize_q100_h2_box_3.webp) Import subscribers to Ghost --------------------------- If all of your subscribers are free, you can import this into Ghost directly. ![import members](/images/docs/migration/import-members-1_huc3d26abd3bec140dac4d1e5fd61f2b53_17353_1000x0_resize_q100_h2_box_3.webp) If you have paid subscribers, you need to relate Stripe Customer IDs with your subscribers emails. ![import members](/images/docs/migration/import-members-2_hu4666f031efd860d74fa4f40b2a587fd0_130604_1000x0_resize_q100_h2_box_3.webp) If you cannot connect your Ghost site to the same Stripe account you used with BeeHiiv, you may need to migrate customer data, products, prices, coupons to a new Stripe account, and then recreate the subscriptions before importing into your Ghost site. The [Ghost Concierge](/concierge/) team can help with this. Migrating Content ----------------- Developers can migrate content from BeeHiiv to Ghost using our [migration CLI tools](https://github.com/TryGhost/migrate/tree/main/packages/mg-beehiiv) . You will first need to [export your posts](https://support.beehiiv.com/hc/en-us/articles/12258595483543-How-to-export-your-post-content) from BeeHiiv. This will be a CSV file which includes all post content, titles, dates, etc. ![export posts](/images/docs/migration/beehiiv-content-export_hu692b89da164ad0d3b6abc7f18ee87332_11063_2148x0_resize_q100_h2_box_3.webp) ![download posts](/images/docs/migration/beehiiv-content-download_hu2bc2e7c63f879a9affdf18f111de440a_16136_2148x0_resize_q100_h2_box_3.webp) First, make sure the CLI is installed. # Install CLI npm install --global @tryghost/migrate # Verify it's installed migrate To run a basic migration with the default commands: # Basic migration migrate beehiiv --posts /path/to/posts.csv --url https://example.com There are [more options](https://github.com/TryGhost/migrate/tree/main/packages/mg-beehiiv#usage) , such as the ability define a default author name and choose where `/subscribe` links go to. Once the CLI task has finished, it creates a new ZIP file which you can [import into Ghost](https://ghost.org/help/imports/) . ### Using custom domains If you’re using a custom domain on BeeHiiv, you’ll need to implement redirects in Ghost to prevent broken links. BeeHiiv uses `/p/` as part of the public post URL, where as Ghost uses it in the URL for post previews. This means the redirect regular expression is quite complex, but necessary so that post previews in Ghost function correctly. # redirects.yaml 301: ^\/p\/(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})(.*): /$1 ^\/polls\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(.*): / ^\/t\/(.*): /tag/$1 302: This means that if a visitor or crawler goes to `https://mysite.com/p/awesome-post`, they will automatically be redirected to `https://mysite.com/awesome-post`. * * * Summary ------- Congratulations on your migration to Ghost 🙌. All that’s left to do is check over your content to ensure the migration has worked as expected. We also have a guide on [how to implement redirects](/docs/tutorials/implementing-redirects/) to make your transition smoother. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from WordPress to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from WordPress and import your content to Ghost with this guide If you're a Ghost(Pro) customer, our team may be able to help you migrate your content and subscribers. Learn more about our [Concierge service](/concierge/) . Self-service Migration ---------------------- Most publishers can do this right now using our [WordPress migrator](https://ghost.org/help/importing-from-wordpress/) . ![Image showing WordPress importer in Settings > Advanced > Import/Export](/images/docs/migration/wordpress/ghost-import_hu962a01aa25518bdd09da163d2805bfcd_160396_2164x0_resize_q100_h2_box_3.webp) ### Supported Content What is supported: * XML files up to 100mb * Up to 2,500 posts * Some shortcodes, such as `[caption]`, `[audio]`, `[code]`, along with most `[vc_]` & `[et_]` based shortcodes from page builder plugins. What’s not supported: * Custom post types * Most uncommon shortcodes * Plugins that alter access to content Complex Migrations ------------------ For more complex migrations, those comfortable with the command line can use our CLI tools. First, install the CLI. Then build and run your command. All options for this tool can be found on [GitHub](https://github.com/TryGhost/migrate/tree/main/packages/mg-wp-xml) . # Install npm install --global @tryghost/migrate # Migrate posts only, add a 'News' tag, retain the existing /yyyy/mm/dd/slug permalink structure migrate wp-xml --pathToFile ./file.xml --pages false --addTag News --datedPermalinks '/yyyy/mm/dd/' Troubleshooting =============== If you’re having trouble exporting an XML file, or have lot of content, try the [API-based tools](https://github.com/TryGhost/migrate/tree/main/packages/mg-wp-api) which can successfully handle migrations with tens of thousands of posts. Authors who were migrated will most likely need to reset their passwords, which can be done when logging into Ghost. * * * Summary ------- Congratulations on your migration to Ghost 🙌. All that’s left to do is check over your content to ensure the migration has worked as expected. We also have a guide on [how to implement redirects](/tutorials/implementing-redirects/#common-redirects) to make your transition smoother. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to install & setup Ghost on Linode Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) A full guide for installing, configuring and running Ghost on your Linode server, for use in production. Overview -------- This the official guide for hosting Ghost on Linode. If you’re comfortable installing, maintaining and updating your own software, this is the place for you. By the end of this guide you’ll have a fully configured Ghost install running in production using MySQL. This install is **not** suitable for [local use](/docs/install/local/) or [contributing](/docs/install/source/) to core. Prerequisites ------------- The officially recommended production installation requires the following stack: * A [configured](https://www.linode.com/docs/guides/getting-started/) and [secured](https://www.linode.com/docs/guides/securing-your-server/) Linode server * A server with at least 1GB memory * * * Server Setup ------------ This part of the guide will ensure all prerequisites are met for installing the Ghost-CLI. ### Setup the server Follow the [official “Getting Started with Linode” guide](https://www.linode.com/docs/guides/getting-started/) to setup your server. You must choose the **Ubuntu image** when creating your server. As you’re creating a new server you should prefer Ubuntu version 22.04 LTS. However you can use Ubuntu 20.04, 18.04 or 16.04 if you want. > Note: Using the user name `ghost` causes conflicts with the Ghost-CLI, so it’s important to use an alternative name. ### Secure your server Follow the [official “How to Secure Your Server” Linode guide](https://www.linode.com/docs/guides/securing-your-server/) to secure your server. ### Install Ghost on Ubuntu Now that you have a secure server with Ubuntu, follow the [Ubuntu](/docs/install/ubuntu/) instructions to install Ghost. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Supported databases in production for self-hosting Ghost - Ghost Developers Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) MySQL 8 is the only supported database in production. As a small team developing open source software that is free to use, we provide a narrow set of environments that are officially supported to keep maintenance manageable. Ghost is built for use with Ubuntu, MySQL 8, nginx and Node.js LTS. MySQL 8 provides features that are optimal for Ghost installations, including the ability to store JSON, and features that help us deliver performance improvements. If database interoperability is something that you are passionate about, consider supporting the [knex](https://github.com/knex/knex) project. How to check your current database in production ------------------------------------------------ * Use `ghost ls` to locate your install and navigate to its location. * Run `ghost config database.client` this should output `sqlite3` or `mysql`. * If it says mysql, run `mysql --version` to find the details of your mysql install. How to update MySQL 5 to MySQL 8 -------------------------------- This will happen automatically if you update your Ubuntu install to [Ubuntu 20](https://www.digitalocean.com/community/tutorials/how-to-upgrade-to-ubuntu-20-04-focal-fossa) . Alternatively you can update MySQL using your package manager of choice. Once your MySQL package is updated, change your Ghost table collations to the new default of `utf8mb4_0900_ai_ci`. Running the following command will generate a set of `ALTER TABLE` commands needed to correct your database: `mysql -u -p -B --disable-column-names -h localhost -e 'SELECT CONCAT("ALTER TABLE ",TABLE_SCHEMA,".",TABLE_NAME," CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; ", "ALTER TABLE ",TABLE_SCHEMA,".",TABLE_NAME," CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;") AS alter_sql FROM information_schema.TABLES WHERE TABLE_SCHEMA = database();'` Once you have the alter table commands, run: `mysql -u -p 'set foreign_key_checks=0; set foreign_key_checks=1;'` How to migrate from MariaDB to MySQL 8 -------------------------------------- We strongly recommend migrating from MariaDB to MySQL 8 — [a helpful guide can be found here](https://forum.ghost.org/t/how-to-migrate-from-mariadb-10-to-mysql-8/29575) . How to migrate from SQLite3 --------------------------- We strongly recommend switching to MySQL 8 by performing a full [reinstall of Ghost](/docs/reinstall) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Newspack to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Newspack and import your content to Ghost with this guide. You can manage a migration from Newspack yourself or, if you prefer, our team can take care of the migration for you. If you're a Ghost(Pro) customer, our team may be able to help you migrate your content and subscribers. Learn more about our [Concierge service](/concierge/) . If you’d rather do the migration yourself, that’s fine too, and you can follow the guide below. Newspack migration steps ------------------------ In order to migrate fully from Newspack to Ghost, here are the different steps you’ll need to take. #### Migrating content Newspack sites run on WordPress, so the first thing you’ll want to do is follow our [WordPress migration guide](/docs/migration/wordpress/) . This will allow you export all your published content, and bring it into Ghost. #### Migrating your theme Ghost comes with several free themes built with news publishers in mind. We suggest starting with [Headline](https://ghost.org/themes/headline/) , which is lightning fast, SEO optimised, and can be further customised to match your brand. #### Migrating email subscribers Ghost can import email subscribers from any platform. Most newspack publishers use Mailchimp, and to migrate contacts you can follow our [Mailchimp migration guide](/docs/migration/mailchimp/) . Email newsletters are built into Ghost natively, so you won’t need to keep paying for a 3rd party service anymore after migrating. #### Migrating paid subscribers Newspack and Ghost both use Stripe for subscription payments, and you can easily import paying subscribers into Ghost by connecting to your Stripe account. When [importing subscribers](https://ghost.org/help/import-members/#prepare-your-csv-file) , make sure to include their Stripe Customer ID, and Ghost will link up the records automatically. If you need help with this, drop us an email on `concierge@ghost.org`. #### Migrating ads & analytics Ghost supports all of the same advertising and analytics services as Newspack, and all of these can be migrated easily. You can paste any needed tracking codes into **Settings → Code Injection**, or you can edit your theme directly to include the code snippets there, if you want more control. #### Migrating URLs For the most part, Ghost will easily match the URL structure of your old site, so any links to your site will keep working as normal. If you have any URLs that have changed, you can take care of these by [setting up redirects](/docs/tutorials/implementing-redirects/) in Ghost. * * * Newspack migration limitations ------------------------------ Ghost has an automatically built-in commenting system for your members and subscribers, but it’s not currently possible to migrate comments from other platforms into Ghost. If you’ve found your comments section is mostly full of spam, though, then you might actually welcome a fresh start. Ghost does not support marketplace listings / directories. If you use this feature of Newspack, this is not something that can be migrated. However, if it’s really important to you, you could always set up a directory on a subdomain of your site - like `listings.yoursite.com`. * * * Newspack migration FAQ ---------------------- **Is migrating from Newspack to Ghost difficult?** Not really! Newspack sites are just WordPress, and we’ve migrated tens of thousands of WordPress sites to Ghost over the years. Most people tend to favour Ghost because it’s a fully integrated platform specifically designed for publishers, rather than a disparate set of CMS plugins. **What about dynamic blocks and pages?** Ghost has those, too. They work very similarly to Newspack, but for the most part they’re much easier to use. Ghost places more emphasis on publishing content with rich media, and less emphasis on dragging/dropping things into complex layouts. We’ve also got [a handy comparison guide](/vs/newspack/) if you want to get a clearer idea of Newspack features compared to Ghost. **Why is Ghost so much cheaper than Newspack** Good question! Newspack is a side-project by WordPress with a small number of customers, so they have to charge a high amount for each customer in order to be able to afford to maintain their product. Ghost is not a side-project, it’s our only project. We have tens of thousands of customers and millions of users, so we don’t need to charge as much per newsroom. **Newspack works with Google News Initiative, won’t I lose that advantage in migrating to Ghost?** Not at all. Ghost has been working with Google News Initiative for years, and we’re proud to be an official technology partner for Google News Initiative bootcamps. We’re thrilled to work with Google on supporting as many local news publishers as we can. **I read that you offer additional support for small newsrooms, what’s that about?** We do! If you run a small local news organisation and would like to chat about how we can support you, get in touch with us by email on `concierge@ghost.org`. **I’m not confident with tech. How can I do these migration steps?** Let our team do them for you, for free. Drop us an email on `concierge@ghost.org` to find out more. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Patreon to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Patreon and import your Patrons to Ghost with this guide Overview -------- Since Patreon manages your subscriptions on your behalf, there is no direct migration path to move your paid subscriptions from Patreon to other platforms. The good news: Ghost makes it possible to import all of your existing patrons and give them access to premium content on a custom Ghost publication, or to sync your Patreon account with Ghost using an automation. [Learn more here](https://ghost.org/resources/patreon-vs-your-own-site/) . Migrating Patrons to Ghost -------------------------- Ghost has an easy to use importer that allows you to migrate a list of members from any other tool, including Patreon. This method is useful if you’re planning to turn signups in Patreon off and have all new members sign up via Ghost, but still need to give your existing Patrons access to your new Ghost Publication. ### Export your subscribers To get started, export your current subscribers in CSV format from [this page](https://www.patreon.com/members) in your Patreon account. ### Import subscribers to Ghost Under the Ghost Admin members settings, select the import option from the settings menu. ![import members](/images/docs/migration/import-members-1_huc3d26abd3bec140dac4d1e5fd61f2b53_17353_1000x0_resize_q100_h2_box_3.webp) Upload your CSV file to Ghost, and map each of the fields contained in your export file to the corresponding fields in Ghost. The **email** field is required in order to create members in Ghost, while the other fields are optional. If you’d like to give these members access to content with an acceess level of `paid-members only` but retain their subscriptions in Patreon, you can give them unlimited access by setting their `complimentary_plan` status to `true` — read more about [Member imports](/help/import-members/) . Once the import has completed, all your subscribers will be migrated to Ghost. There’s nothing else you need to do, members can now log into your site and receive email newsletters. Running Ghost alongside Patreon ------------------------------- It’s also possible to use Zapier or the Ghost API to keep your Patrons and Members in sync in both platforms. This is useful if you’re giving your audience on Patreon access to premium content on a custom Ghost site as an additional perk, or if you’re accepting signups on both platforms. To find out how to connect Ghost with Patreon, check out our [integration](/integrations/patreon/) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Major Versions & Long Term Support - Ghost Developers Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Major version release dates and end of life support for Ghost. | Version | Released | End of Life | | --- | --- | --- | | Ghost 0.x | 2013 | Jan 2019 | | Ghost 1.x | 2017 | Jan 2020 | | Ghost 2.x | 2018 | Jan 2021 | | Ghost 3.x | 2019 | Jan 2022 | | Ghost 4.x | 2021 | Jan 2023 | | Ghost 5.x (Current) | 2022 | TBC | At any one time the current major version of Ghost is under active development, whilst the previous version is maintained as the Long Term Support version (LTS). The **current** version of Ghost receives new features and improvements on a regular basis. The **LTS** version of Ghost will only receive critical security and stability fixes. Once a version reaches End of Life it should be considered insecure. The [update guide](/docs/update/) describes the steps for updating from Ghost 1.0 or higher, to the **latest version** of Ghost. If you’re running an 0.x version of Ghost, you’ll need to follow the [update guide](/docs/faq/update-0x/) first to update to 1.0, as there is no direct update path from versions < 1.0 to the latest version. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Squarespace to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Squarespace and import your content to Ghost with this guide If you're a Ghost(Pro) customer, our team may be able to help you migrate your content and subscribers. Learn more about our [Concierge service](/concierge/) . Migrations from Squarespace are a complex manual process with a lot of data sanitisation work. If you want to do a migration yourself, you’ll need to follow our [developer documentation](/docs/migration/custom/) to create your own migration archive. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Medium to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Medium and import your content to Ghost with this guide Self-service Migration ---------------------- Most publishers can do this right now using our [Medium migrator](https://ghost.org/help/importing-from-medium/) . ![Image showing Medium importer in Settings > Advanced > Import/Export](/images/docs/migration/wordpress/ghost-import_hu962a01aa25518bdd09da163d2805bfcd_160396_2164x0_resize_q100_h2_box_3.webp) ### Using custom domains If you’re using a custom domain on Medium, you’ll need to implement redirects in Ghost to prevent broken links. Medium appends a small random ID to each post, which is removed in the migration step above. The regular expression below removes that random ID, but does not affect preview links. # redirects.yaml 301: ^\/(?!p\/?)(.*)(-[0-9a-f]{10,12}): /$1 302: This means that if a visitor or crawler goes to `https://mysite.com/awesome-post-a1b2c3d4e5f6`, they will automatically be redirected to `https://mysite.com/awesome-post`. * * * Summary ------- Congratulations on your migration to Ghost 🙌. All that’s left to do is check over your content to ensure the migration has worked as expected. We also have a guide on [how to implement redirects](/docs/tutorials/implementing-redirects/) to make your transition smoother. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to migrate data from Ghost to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from a self-hosted instance to Ghost(Pro) with this guide If you're a Ghost(Pro) customer, our team may be able to help you migrate your content and subscribers. Learn more about our [Concierge service](/concierge/) . This guide will walk you through the process of migrating from a self-hosted Ghost instance on your own server to Ghost(Pro). Prerequisites ------------- If your self-hosted site is running an older major version of Ghost, you may need to update. Check the latest [version of Ghost on GitHub](https://github.com/TryGhost/Ghost/releases) , and follow this [upgrade guide](/docs/update/) . Back up your data ----------------- The first step towards moving from your own self-hosted Ghost instance to Ghost(Pro) is to retrieve all of your data from your server to your local machine. It’s best to do this first, to ensure you have a backup in place. > The commands in this guide assume you followed our [Ubuntu guide](/docs/install/ubuntu/) > to set up your own instance. If you used another method, you’ll need to adapt the paths in the commands to suit. ### Exporting content Log into Ghost Admin for your self-hosted in production and navigate to the **Labs** view, and click **Export** to download your content. This will be `.json` file, with a name like `my-site.ghost.2020-09-30-14-15-49.json`. ![content import export](/images/docs/migration/self-hosted/ghost-content-import-export_hu272562aa500e83b9baeec6c17282ce1a_25089_1628x0_resize_q100_h2_box_3.webp) ### Routes and redirects Staying on the **Labs** page, click **Download current redirects** to get your redirects file. This will be called `redirects.yaml` (or `redirects.json` depending on your Ghost version). If you’re using custom routes, click **Download current routes.yaml** to get your `routes.yaml` file. ![routes redirects](/images/docs/migration/self-hosted/ghost-route-redirects_hu954a63ee77a86db320db7d009e6fb2e9_29801_1628x0_resize_q100_h2_box_3.webp) ### Themes Navigate to the **Design** view, and click the **Download** button next to the Active label export your current theme. This will be a `.zip` file. Optionally, if you have other themes that you’d like to save, download them and back them up. ![themes settings](/images/docs/migration/self-hosted/ghost-themes-settings_hu3b859fed207678d52a22902f80176aaf_12909_1628x0_resize_q100_h2_box_3.webp) ### Images To download your images, you’ll need shell access to your server. If you’re unable to gain shell access to your current web host, you may need to contact their support team and ask for a zip of your images directory. Once you’re logged in to your server, `cd` to the `content` directory: cd /var/www/ghost/content And then `zip` the `images` directory with all its contents: zip -r images.zip images/* Ensure your `images` folder only contains images. Any other file types may cause import errors. Now we need to get that zip file from your server onto your local machine: scp user@123.456.789.123:/var/www/ghost/content/images.zip ~/Desktop/images.zip The folder structure should look like this, with `images` being the only top-level folder once unzipped: ![images in finder](/images/docs/migration/self-hosted/images-in-finder_huf248f9006ca4711e6e56a11852458172_99427_1260x0_resize_q100_h2_box.webp) Uploading to Ghost(Pro) ----------------------- Once you’ve retrieved all of these exports, you can upload them to Ghost(Pro) in the same order. ### Content Log into your new Ghost(Pro) site, and head to the **Labs** view. Next to the **Import content** header, select your content `.json` file and click **Import**. ![user import successful](/images/docs/migration/self-hosted/ghost-import-successful_huade8b63d13532484da2176e269d2dc02_38287_1628x0_resize_q100_h2_box_3.webp) ### Routes and Redirects Staying on the **Labs** view, click **Upload redirects JSON**, then select your `redirects.json` file to upload it. Then click **Upload routes YAML**, select your `routes.yaml` file to upload that. ### Themes Head over to the **Design** view, and click **Upload a theme**, select your theme `.zip` file, and activate it. ![activate theme](/images/docs/migration/self-hosted/ghost-theme-upload_huf4a027a768e71e6bd93af4733d14943e_23711_1226x0_resize_q100_h2_box_3.webp) ### Images The final step is to upload your images. The best way to approach this depends on how big your `images.zip` file is. A large file will take longer to upload and process. If your file is less than 500mb, you can upload this zip in the same way you uploaded your content JSON file. If the file is larger, it’s recommended to split it into multiple smaller files, whilst retaining the folder structure. If you have a large image directory or encounter any errors, contact support so we can help upload your images. * * * Summary ------- Congratulations on moving to Ghost(Pro). All that’s left to do is check over your content to ensure everything works as expected. By hosting your site with us, you directly fund future product development of Ghost itself and allow us to make the product better for everyone 💘 Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Handlebars Theme Helpers: search Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Usage: `{{search}}` Description ----------- The `{{search}}` helper outputs a search icon button that launches Ghost search when clicked. The color of the icon uses the `currentColor` CSS property, meaning it will match the color of text around it. The styling can be overriden by using the `.gh-search-icon` class plus `!important`. ### Example Code {{search}} The helper will output the following markup: For other ways to launch Ghost search and to learn more about this feature, [see the Ghost search documentation](https://ghost.org/docs/themes/search/) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Buttondown to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Buttondown and import your content to Ghost with this guide If you're a Ghost(Pro) customer, our team may be able to help you migrate your content and subscribers. Learn more about our [Concierge service](/concierge/) . Export your subscribers ----------------------- To get started, export your current subscribers in CSV format. Import subscribers to Ghost --------------------------- Under the Ghost Admin members settings, select the import option from the settings menu. ![import members](/images/docs/migration/import-members-1_huc3d26abd3bec140dac4d1e5fd61f2b53_17353_1000x0_resize_q100_h2_box_3.webp) Upload your CSV file to Ghost, and map each of the fields contained in your export file to the corresponding fields in Ghost. The **email** field is required in order to create members in Ghost, while the other fields are optional. To import paid members with an existing Stripe subscription, you must import their **Stripe customer ID**. ![import members](/images/docs/migration/import-members-2_hu4666f031efd860d74fa4f40b2a587fd0_130604_1000x0_resize_q100_h2_box_3.webp) Once the import has completed, all your subscribers will be migrated to Ghost. There’s nothing else you need to do, members can now log into your site and receive email newsletters. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Kit to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Kit and import your subscribers to Ghost with this guide Overview -------- Since Kit manages subscriptions on your behalf, there is no direct migration path to move any paid subscriptions from Kit to other platforms. The good news: Ghost makes it possible to import all of your existing subscriber emails and give them access to premium content on your custom Ghost publication, or to sync your Kit subscribers with Ghost using an automation. Export your subscribers ----------------------- To get started, export your current subscribers from Kit in CSV format. ![import members](/images/docs/migration/export-convertkit_huc44eb2f23f6040a066f2331419f0a0c1_32934_2336x0_resize_q100_h2_box_3.webp) Import subscribers to Ghost --------------------------- Under the Ghost Admin members settings, select the import option from the settings menu. ![import members](/images/docs/migration/import-members-1_huc3d26abd3bec140dac4d1e5fd61f2b53_17353_1000x0_resize_q100_h2_box_3.webp) Upload your CSV file to Ghost, and map each of the fields contained in your export file to the corresponding fields in Ghost. The **email** field is required in order to create members in Ghost, while the other fields are optional. It’s recommended to edit your data as required before uploading your CSV file to Ghost. Once the import has completed, all your subscribers will be migrated to Ghost. There’s nothing else you need to do, members can now log into your site and receive email newsletters. Running Ghost alongside Kit --------------------------- It’s also possible to use Zapier or the Ghost API to keep email subscribers from Kit in sync with a Ghost membership site. This is useful if you’re giving your existing audience in Kit access to premium content on a your Ghost site as an additional perk, or if you’re accepting signups on both platforms. To find out how to connect Ghost with Kit, check out our [integration](/integrations/convertkit/) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Gumroad to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Gumroad and import your customers to Ghost with this guide Overview -------- Since Gumroad manages your subscriptions on your behalf, there is no direct migration path to move your paid subscriptions from Gumroad to other platforms. The good news: Ghost makes it possible to import all of your existing customer emails and give them access to premium content, or to sync your Gumroad with your Ghost site using an automation. Export your customers --------------------- To get started, export your current subscribers from Gumroad in CSV format. ![export members from gumroad](/images/docs/migration/gumroad-export_hu56c76fc0713e6fce5586b59ad57c30de_7136_1520x0_resize_q100_h2_box_3.webp) Gumroad allows you to export all customers who have ever purchased from you within a specific date range, or to segment your export per product. Import subscribers to Ghost --------------------------- Under the Ghost Admin members settings, select the import option from the settings menu. ![import members](/images/docs/migration/import-members-1_huc3d26abd3bec140dac4d1e5fd61f2b53_17353_1000x0_resize_q100_h2_box_3.webp) Upload your CSV file to Ghost, and map each of the fields contained in your export file to the corresponding fields in Ghost. The **email** field is required in order to create members in Ghost, while the other fields are optional. It’s recommended to edit your data as required before uploading your CSV file to Ghost. Once the import has completed, all your subscribers will be migrated to Ghost. There’s nothing else you need to do, members can now log into your site and receive email newsletters. Running Ghost alongside Gumroad ------------------------------- It’s also possible to use Zapier or the Ghost API to keep customers who have purchased from you on Gumroad in sync with a Ghost membership site. This is useful if you’re giving your existing customers on Gumroad access to premium content on a custom Ghost site as an additional perk, or if you’re accepting signups on both platforms. To find out how to connect Ghost with Gumroad, check out our [integration](/integrations/gumroad/) . Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # How to backup your self-hosted Ghost install - Ghost Developers Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Learn how to backup your self-hosted Ghost install When performing manual updates it’s always recommended to make a full backup of your site first, so if anything goes wrong, you’ll still have all your data. The fastest way to perform a backup is to use Ghost CLI to automatically generate a zip file containing all of your site data using the `ghost backup` command. If you need to perform a manual backup of your data, the following guide walks you through all of the steps. ### Export content Log into Ghost Admin, navigate to the **Labs** view, and click **Export** to download all content. This will be a `.json` file, with a name like `my-site.ghost.2020-09-30-14-15-49.json`. ![Screenshot of content export in Ghost Admin](/images/docs/faq/download-content_hua52806e65860e327ac2a06c0b285d95b_21881_1722x0_resize_q100_h2_box_3.webp) ### Download routes and redirects Staying on the **Labs** page, click **Download current redirects** to get your redirects file. This will be called `redirects.yaml` or `redirects.json` depending on your Ghost version. If you’re using custom routes, click **Download current routes.yaml** to get your `routes.yaml` file. ![Screenshot of routing and redirects export in Ghost Admin](/images/docs/faq/download-routes-redirects_hub0b987ca0988930e4cb2d6cb22c57b87_19346_1738x0_resize_q100_h2_box_3.webp) ### Download themes Navigate to the **Design** settings page, and navigate to the themes section to download your active theme, and any other themes you want to store. This will be a `.zip` file. ![Screenshot theme install and download page in Ghost Admin](/images/docs/faq/download-themes_hu358079c312968754f2a55fd07e1e0d75_497694_2332x0_resize_q100_h2_box_3.webp) ### Copy images, files and media To download images and media, you’ll need shell access to your server. If you’re unable to gain shell access to your current web host, you may need to contact their support team. When logged in to your server, `cd` to the `content` directory: `cd /var/www/ghost/content` Then, `zip` the `images` and other file storage directories with their contents `zip -r content-files.zip images/ files/ media/` Then, to move the zip files from your server onto your local machine. `scp user@123.456.789.123:/var/www/ghost/content/content-files.zip ~/Desktop/content-files.zip` ### Export members Export all members in one CSV file from the Members dashboard in Ghost Admin. ![Screenshot of member download in Ghost Admin](/images/docs/faq/export-members_hua4d8503095287f368fad3a048eba2785_16838_1018x0_resize_q100_h2_box_3.webp) * * * Restoring your data from a manual backup ---------------------------------------- The following steps explain how to restore data from a manual backup. ### Copy images, files and media All images, files and media need to be copied over to your new install. After doing that, run this command to fix permissions: `sudo chown -R ghost:ghost content` Once this is complete, restart Ghost: `ghost restart` ### Import content To begin the content migration, head to the **Labs** section of Ghost Admin and import the `ghost.json` backup which you exported earlier. ### Upload routes and redirects From the Labs page, import the `routes.yaml` and `redirects.yaml` files that you previously downloaded for your backup data. ### Upload theme Next, upload your theme in the **Design** settings page of Ghost Admin to get your site looking the same way it did before. ### Reconnect Stripe _Skip this step if you’re not using Stripe for paid subscriptions._ To import paid members, Ghost needs to be connected to Stripe in Live mode before you import your members. Make sure to connect Ghost to **the same Stripe account** you were using on your old installation - learn more about how to connect a Stripe account in [this guide](/help/setup-members/#connect-a-stripe-account) . ### Import members With Stripe connected, you can now import your members CSV file. You’ll receive an email notification when the import process has completed. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Memberful to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Memberful and import your members to Ghost with this guide If you're a Ghost(Pro) customer, our team may be able to help you migrate your content and subscribers. Learn more about our [Concierge service](/concierge/) . Export your subscribers ----------------------- To get started, export your current subscribers in CSV format. Import subscribers to Ghost --------------------------- Under the Ghost Admin members settings, select the import option from the settings menu. ![import members](/images/docs/migration/import-members-1_huc3d26abd3bec140dac4d1e5fd61f2b53_17353_1000x0_resize_q100_h2_box_3.webp) Upload your CSV file to Ghost, and map each of the fields contained in your export file to the corresponding fields in Ghost. The **email** field is required in order to create members in Ghost, while the other fields are optional. If you’d like to give these members access to content with an access level of `paid-members only` but retain their subscriptions in Memberful, you can give them unlimited access by setting their `complimentary_plan` status to `true` — read more about [Member imports](/help/import-members/) . Once the import has completed, all your subscribers will be migrated to Ghost. There’s nothing else you need to do, members can now log into your site and receive email newsletters. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Jekyll to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Jekyll and import your content to Ghost with this guide Migrations from Jekyll are a complex manual process with a lot of data sanitisation work. If you want to do a migration yourself, you’ll need to follow our [developer documentation](/docs/migration/custom/) to create your own migration archive. Jekyll users can try the [Jekyll to Ghost Plugin](https://github.com/mekomlusa/Jekyll-to-Ghost) Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Migrating to Ghost - Developer Guide Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) If no export tools exist for your current blogging system you’ll need to create one that generates a JSON file as described here. There is a full example at the end of this file. Please note that your final JSON file should have no comments in the final format. Those are only included here for readability and explanatory purposes. ### JSON file structure First and foremost, your JSON file must contain valid JSON. You can test your file is valid using the [JSONLint](https://jsonlint.com/) online tool. The file structure can optionally be wrapped in: { "db": [...contents here...] } Both with and without are valid Ghost JSON files. But you must include a `meta` and a `data` object. ### The meta object "meta": { "exported_on":1408552443891, "version":"2.14.0" } The `meta` block expects two keys, `exported_on` and `version`. `exported_on` should be an epoch timestamp in milliseconds, version should be the Ghost version the import is valid for. ### The data block Ghost’s JSON format mirrors the underlying database structure, rather than the API, as it allows you to override fields that the API would ignore. "data": { "posts": [{...}, ...], "tags": [], "users": [], "posts_tags": [], "posts_authors": [], "roles_users": [] } The data block contains all of the individual post, tag, and user resources that you want to import into your site, as well as the relationships between all of these resources. Each item that you include should be an array of objects. Relationships can be defined between posts and tags, posts and users (authors) and between users and their roles. IDs inside the file are relative to the file only, so if you have a `post` with `id: 1` and a `posts_tags` object which references `post_id: 1`, then those two things will be linked, but they do not relate to the `post` with `id: 1` in your database. Example ------- { "meta":{ // epoch time in milliseconds "exported_on": 1388805572000, "version": "2.14.0" }, "data":{ "posts": [\ {\ "id": 1,\ "title": "my blog post title",\ "slug": "my-blog-post-title", // Optional, will be generated by Ghost if missing\ // Lexical is used to represent your content\ "lexical": "{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello, beautiful world! 👋\",\"type\":\"extended-text\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":\"ltr\",\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}",\ "feature_image": null,\ "feature_image_alt": null,\ "feature_image_caption": null,\ "featured": 0, // boolean indicating featured status\ "page": 0, // boolean indicating if this is a page or post\ "status": "published", // or draft\ "published_at": 1283780649000, // epoch time in milliseconds\ "published_by": 1, // the first user created has an id of 1\ "meta_title": null,\ "meta_description":null,\ "email_only": false, // boolean indicating email-only type of post\ "author_id": 1, // the first user created has an id of 1\ "created_at": 1283780649000, // epoch time in milliseconds\ "created_by": 1, // the first user created has an id of 1\ "updated_at": 1286958624000, // epoch time in milliseconds\ "updated_by": 1 // the first user created has an id of 1\ }\ ], "tags": [\ {\ "id": 5,\ "name": "Colorado Ho!",\ "slug": "colorado-ho", // Optional, will be generated by Ghost if missing\ "description": ""\ }\ ], "posts_tags": [\ {"tag_id": 5, "post_id": 1},\ ], "users": [\ {\ "id": 3,\ "name": "Jo Bloggs",\ "slug": "jo-blogs", // Optional, will be generated by Ghost if missing\ "email": "jo@example.com",\ "profile_image": null,\ "cover_image": null,\ "bio": null,\ "website": null,\ "location": null,\ "accessibility": null,\ "meta_title": null,\ "meta_description": null,\ "created_at": 1283780649000, // epoch time in millis\ "created_by": 1,\ "updated_at": 1286958624000, // epoch time in millis\ "updated_by": 1\ }\ ], // Provide this if you want different roles to the default "Author" role "roles_users": [\ {\ "user_id": 2,\ "role_id": ObjectId // This must reference the id from your database\ }\ ] } } Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Handlebars Theme Helpers: foreach Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Usage: `{{#foreach data}}{{/foreach}}` Description ----------- `{{#foreach}}` is a special loop helper designed for working with lists of posts. It can also iterate over lists of tags or users if needed. The foreach helper will output the content placed between its opening and closing tags `{{#foreach}}{{/foreach}}` once for each item in the collection passed to it. The `{{#foreach}}` helper is context-aware and should **always** be used instead of Handlebars `each` when working with Ghost themes. ### Simple Example The main use of the `{{#foreach}}` helper in Ghost is iterating over the posts to display a list of posts on your home page, etc: {{#foreach posts}}

{{title}}

{{excerpt words="26"}} »

{{/foreach}} Data Variables -------------- When inside a `{{#foreach}}` block, you have access to a set of data variables about the current iteration. These are: * **@index** (number) - the 0-based index of the current iteration * **@number** (number) - the 1-based index of the current iteration * **@key** (string) - if iterating over an object, rather than an array, this contains the object key * **@first** (boolean) - true if this is the first iteration of the collection * **@last** (boolean) - true if this is the last iteration of the collection * **@odd** (boolean) - true if the @index is odd * **@even** (boolean) - true if the @index is even * **@rowStart** (boolean) - true if `columns` is passed and this iteration signals a row start * **@rowEnd** (boolean) - true if `columns` is passed and this iteration signals a row’s end Usage ----- `{{#foreach}}` is a block helper. The most common use case in Ghost is looping through posts. {{#foreach posts}}

{{title}}

{{excerpt}}

{{/foreach}} ### {{else}} and negation Like all block helpers, `{{#foreach}}` supports adding an `{{else}}` block, which will be executed if there is no data to iterate over: {{#foreach tags}} {{name}} {{else}}

There were no tags...

{{/foreach}} ### The `limit` attribute Passing `{{#foreach}}` a `limit` attribute will tell it to stop after a certain number of iterations. {{#foreach posts limit="3"}} {{name}} {{/foreach}} Note that as the `{{#foreach}}` helper is only passively iterating over data, not actively fetching it, if you set the limit to a number higher than the number of items in the collection, it will have no effect. ### The `from` and `to` attributes Passing `{{#foreach}}` a `from` or `to` attribute will change the items that are output. Both attributes are 1-indexed and inclusive, so `from="2"` means from and including the 2nd post. {{#foreach posts from="2" to="5"}} {{name}} {{/foreach}} ### The `visibility` attribute By default, `foreach` only displays data that is public. This means that data like hidden tiers and internal tags won’t be included. Set `visibility` to `all` to show all data or to `none` to show hidden data. {{#foreach tags visibility="all"}}

{{name}}

{{/foreach}} ## Data variable examples ### `@index`, `@number` and `@key` `{{@index}}` is the 0-based index of the collection - that is the "count" of the loop. It starts at 0 and then each time around the loop, `{{@index}}` increases by 1. This is useful for adding numbered classes: ```handlebars {{#foreach posts}}
{{title}}
{{/foreach}} `{{@number}}` is very similar to `@index`, but starts at 1 instead of 0, which is useful for outputting numbers you want users to see, e.g. in styled numbered lists:
    {{#foreach posts}}
  1. {{title}}
  2. {{/foreach}}
`{{@key}}` will contain the object key, in the case where you iterate over an object, rather than an array. There’s no real use case for this in Ghost at present. #### `@first` & `@last` The following example checks through an array or object, `posts`, and tests for the first entry. {{#foreach posts}} {{#if @first}}
First post
{{/if}} {{/foreach}} We can also nest `if` statements to check multiple properties. In this example, we separate the output of the first and last posts from the other posts. {{#foreach posts}} {{#if @first}}
First post
{{else}} {{#if @last}}
Last post
{{else}}
All other posts
{{/if}} {{/if}} {{/foreach}} #### `@even` & `@odd` The following example adds a class of even or odd, which could be used for zebra striping content: {{#foreach posts}}
{{title}}
{{/foreach}} #### `@rowStart` & `@rowEnd` `@rowStart` and `@rowEnd` return `true` at the beginning and end of a column respectively when the `columns` value is set in a `#foreach`. In the following example, the posts are being grouped up in threes with a wrapping `div` element: {{#foreach posts columns="3"}} {{#if @rowStart}}
{{/if}} {{title}} {{#if @rowEnd}}
{{/if}} {{/foreach}} Block Params ------------ Block params allow you to name the individual item being operated on inside the loop, For example: {{#foreach posts as |my_post|}} {{#my_post}}

{{title}}

{{/my_post}} {{/foreach}} Which is much the same as doing `posts.forEach(function (my_post) {}` in JavaScript. Useful with advanced features like the `{{get}}` helper. Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Official guide: How to migrate from Mailchimp to Ghost Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Migrate from Mailchimp and import your content to Ghost with this guide Self-service Migration ---------------------- Most publishers can do this right now using our [Mailchimp migrator](https://ghost.org/help/importing-from-mailchimp/) . ![Image showing Mailchimp importer in Settings > Advanced > Import/Export](/images/docs/migration/wordpress/ghost-import_hu962a01aa25518bdd09da163d2805bfcd_160396_2164x0_resize_q100_h2_box_3.webp) Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Handlebars Theme Helpers: if Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Usage: `{{#if featured}}{{/if}}` The `{{#if}}` block helper comes built in with Handlebars. ### Description `{{#if}}` allows for testing very simple conditionals, and executing different template blocks depending on the outcome. The conditionals that can be tested are very simple, essentially only checking for ’truthiness’. The evaluation rules are explained in the section below. Like all block helpers, `{{#if}}` supports adding an `{{else}}` block or using `^` instead of `#` for negation - this means that the `{{#if}}` and `{{else}}` blocks are reversed if you use `{{^if}}` and {{else}} instead. In addition, it is possible to do `{{else if ...}}`, to chain together multiple options like a switch statement. #### Evaluation rules The if helper takes a single value, and evaluates whether it is true or false. Any passed in value which is equivalent to `false`, `0`, `undefined`, `null`, `""` (an empty string) or `[]` (an empty array) is considered false, and any other value is considered true. * Any boolean value, like the featured flag on a post, will evaluate to true or false as you expect. * Any string value will be true, as long as it is not null or empty * All numerical values, with the exception of `0` evaluate to true, 0 is the same as false * Any property which doesn’t exist or is not set will always evaluate false * Empty arrays or objects will be false ### Example code When in the scope of a post, `featured` is a boolean flag. The following code example will evaluate to true only if the post is marked as featured. {{#post}} {{#if featured}} ...do something if the post is featured... {{/if}} {{/post}} You can also use this to test if any property is set. Strings, like image URLs will evaluate to true as long as one is present, and will be null (false) otherwise: {{#post}} {{#if feature_image}} {{else}} {{/if}} {{else}}

No posts to display!

{{/post}} Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Handlebars Theme Helpers: has Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Usage: `{{#has tag="value1,value2" author="value"}}` `{{#has slug=../slug}}` `{{#has number="nth:3"}}` `{{#has any="twitter, facebook"}}` `{{#has all="twitter, facebook"}}` Description ----------- `{{#has}}` is like `{{#if}}` but with the ability to do more than test a boolean. It allows theme developers to ask questions about the current context and provide more flexibility for creating different layouts. Like all block helpers, `{{#has}}` supports adding an `{{else}}` block or using `^` instead of `#` for negation - this means that the `{{#has}}` and `{{else}}` blocks are reversed if you use `{{^has}}` and `{{else}}` instead. In addition, it is possible to do `{{else has ...}}`, to chain together multiple options like a switch statement. ### Simple Example The `{{#has}}` helper can be combined with internal tags, to display different information for different types of posts. E.g. implementing a link-style post by adding an internal tag of `#link` and using the has helper to detect it: {{#post}} {{#has tag="#link"}} {{> "link-card"}} {{else}} {{> "post-card"}} {{/has}} {{/post}} Usage ----- The `{{#has}}` helper supports four different types of “questions”: * Post has tag or author * Context has slug or id * Context has any or all properties set * Foreach loop number or index Questions are asked by providing attribute-value pairs, e.g. `tag="tag name"`. You can pass multiple attributes, and the `{{#has}}` helper will always treat this as an `OR`. E.g. You can look for a post with a slug of “welcome” OR a tag of “getting started”: {{#has slug="welcome" tag="getting started"}} ...Will execute if the slug is welcome OR the tag is getting-started... {{/has}} ### Post tag or author #### Comma Separated List {{#has tag="photo"}}{{/has}} {{#has tag="photo, video"}}{{/has}} {{#has author="Joanna Bloggs"}}{{/has}} Specifically when inside the context of a post, you can use the `{{#has}}` helper to find out if the post has a particular tag or author. Both the `tag` and `author` attributes take a comma separated list. If you pass multiple values separated by a comma, these will be treated as an OR. {{#has tag="General, News"}} ...Will execute if the post has a tag of General or News... {{/has}} Tag and author matching is a lowercase match on the tag name or author name, which ignores special characters. #### Counting The `author` and `tag` attribute accepts a counting value. You can choose between: * `count:[number]` * `count:>[number]` * `count:<[number]` This functionality can be helpful when designing a theme. You can change the behaviour if a post has only one author or more than 1. {{#has tag="count:1"}}{{/has}} {{#has tag="count:>1"}}{{/has}} {{#has author="count:<2"}}{{/has}} ### Slug or id {{#has slug="welcome"}}{{/has}} {{#has slug=../../slug}}{{/has}} {{#has id=post.id}}{{/has}} If you’re in the context of an object that has a slug (e.g. post, author, tag and navigation items) you can use the `{{#has}}` helper to do an exact match. Similarly for all objects that have an ID. You can either pass the `{{#has}}` helper a string wrapped in quotes, or a path to a data value from else where in the template data. For example, the following code does an exact match on the string “welcome”. If the post’s slug is the same, the code inside the has helper will execute. {{#has slug="welcome"}} ... do something.. {{/has}} Alternatively, you can pass a handlebars path, which references a different piece of data to match against: {{#has slug=../post.slug}} ...do something... {{/has}} ### Any or all The `any` comparison will return true if **any** one of the properties is set in the current context, with support for paths and globals: {{#has any="twitter, facebook, website"}} {{#has any="author.facebook, author.twitter,author.website"}} {{#has any="@site.facebook, @site.twitter"}} Similarly, the `all` comparison will return true only when **all** of the properties are set: {{#has all="@labs.subscribers,@labs.publicAPI"}} ### Foreach loop number or index {{#has number="3"}}{{/has}} // A single number {{#has number="3, 6, 9"}}{{/has}} // list of numbers {{#has number="nth:3"}}{{/has}} // special syntax for nth item {{!-- All of these work exactly the same for index --}} When you’re inside a `{{#foreach}}` loop of any kind, you have access to two special data variables called `@index` and `@number`. `@index` contains the 0-based index or count of the loop, and `@number` contains a 1-based index. That is each time around the loop these values increase by 1, but `@index` starts at 0, and `@number` starts at 1. The `{{#has}}` helper will let you check which number/index of the iteration you are on using the 3 different styles of matching shown above. For example, if you have a list of posts and want to inject a special widget partial every 3rd post, you could do so using the `nth:3` pattern: {{#foreach posts}} {{#has number="nth:3"}} {{> "widget"}} {{/has}} {{> "post-card"}} {{/foreach}} Example Code ------------ To determine if a post has a particular tag: {{#post}} {{#has tag="photo"}} ...do something if this post has a tag of photo... {{else}} ...do something if this posts doesn't have a tag of photo... {{/has}} {{/post}} You can also supply a comma-separated list of tags, which is the equivalent of an OR query, asking if a post has any one of the given keywords: {{#has tag="photo, video, audio"}} ...do something if this post has a tag of photo or video or audio... {{else}} ...do something with other posts... {{/has}} You can do an AND query by nesting your `{{#has}}` helpers: {{#has tag="photo"}} ...do something if this post has a tag of photo.. {{#has tag="panorama"}} ...if the post has both the photo and panorama tags {{/has}} {{else}} ...do something with other posts... {{/has}} Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) --- # Ghost Handlebars Theme Helpers: match Common searches * [how to set up my custom domain](/help/using-custom-domains/) * [how to install ghost](/docs/install/) * [login not working, how to reset password](/help/how-do-i-reset-my-password/) * [cloudflare setup and config](/help/cloudflare-domain-setup/) * [how to make a ghost theme](/docs/themes/) [Developer docs](/docs/) Quick-search for anything ⌘F [5.118.0](https://github.com/tryghost/ghost/) Usage: `{{#match @custom.color_scheme "=" "Dark"}} class="dark-mode"{{/match}}` ### Description `{{#match}}` allows for simple comparisons, and executing different template blocks depending on the outcome. Like all block helpers, `{{#match}}` supports adding an `{{else}}` block or using `^` instead of `#` for negation - this means that the `{{#match}}` and `{{else}}` blocks are reversed if you use `{{^match}}` and `{{else}}` instead. In addition, it is possible to do `{{else match ...}}`, to chain together multiple options like a switch statement. ### Operators #### Equality `match` supports comparing values for equality, which is the default behaviour: {{#match @custom.color_scheme "=" "Dark"}}...{{else}}...{{/match}} {{!-- Can be shortened to: --}} {{#match @custom.color_scheme "Dark"}}...{{else}}...{{/match}} The equality test can also be negated: {{#match @custom.color_scheme "!=" "Dark"}}...{{else}}...{{/match}} #### Numeric comparisons The match handler supports >, <, >= and <= operators for numeric comparisons. {{#match posts.length ">" 1}}...{{else}}...{{/match}} #### Evaluation rules Values passed to `match` are tested according to their _value_ as well as their _type_. For example: {{!-- Returns true/false --}} {{#match feature_image true}}...{{else}}...{{/match}} {{!-- Always returns false --}} {{#match feature_image 'true'}}...{{else}}...{{/match}} `match` can also be used to test boolean values similar to `if`: {{!-- Default behaviour is to test if a value is truthy --}} {{#match featured}}...{{else}}...{{/match}} ### Example code The `match` helper is extremely useful when combined with [Custom settings](/docs/themes/custom-settings/) using `@custom`: {{!-- Adds the 'font-alt' class when the Typography setting is set to 'Elegant serif' --}} Launch your site ---------------- Last week, 10,502 brand new publications got started with Ghost. Today, it's your turn. [Start a free trial now →](https://account.ghost.org/signup/) Product * [Creator platform](/) * [Theme marketplace](/marketplace/) * [Integrations](/integrations/) * [Experts](/experts/) * [Ghost for news](/news/) Developers * [How to install Ghost](/docs/install/) * [Core concepts](/docs/) * [Ghost hosting](/pricing/) * [API documentation](/docs/content-api/) * [Security overview](/docs/security/) * [Source code](https://github.com/TryGhost/Ghost) Resources * [Ghost tutorials](/tutorials/) * [Resources](/resources/) * [Node.js CMS guide](https://nodecms.guide) * [Open Subscription Platforms](https://opensubscriptionplatforms.com) Comparisons * [Ghost vs Substack](/vs/substack/) * [Ghost vs WordPress](/vs/wordpress/) * [Ghost vs Medium](/vs/medium/) * [Ghost vs Memberful](/vs/memberful/) * [Ghost vs Patreon](/vs/patreon/) * [Ghost alternatives →](/alternatives/) Support * [Help center](/help/) * [Community forum](https://forum.ghost.org/) * [Status   Triangle  99.9%](https://status.ghost.org/) [![Non-Profit Foundation](/images/logos/indie.svg)](/about/) [![Open Source](/images/logos/opensource.svg)](https://github.com/tryghost) [![Carbon Neutral](/images/logos/carbonneutral.svg)](https://climate.stripe.com/6MNofu) ---