Open-source, SEO-ready CMS engine for Rails with structured data, multilingual publishing, version history, and a rich-text editor.
27
stars
3
commits
JavaScript
primary language
Aug 31, 2026
updated
PostnHost is an open source CMS engine built with Rails, Hotwire, and TailwindCSS. Write, translate, and publish articles with a rich text editor, full version history, and proper SEO that works out of the box.
Mount this engine into an existing Rails app, or use the postnhost-app repository for a pre-configured, self-hosted Rails application.

Add PostnHost to your host application's Gemfile:
gem "postnhost"
Run the installer:
bundle install
bin/rails g postnhost:install
This will:
config/initializers/postnhost.rb with configuration optionsconfig/initializers/carrierwave.rb (if not exists)Run migrations:
bin/rails postnhost:install:migrations
bin/rails db:migrate
If there are no users yet, visit /blog/onboarding to create the first CMS administrator with credentials you choose. This is the default installer path; if you mount the engine somewhere else, open <mount-path>/onboarding. The setup flow can add optional sample content after the account is created.
Alternatively, create a CMS user interactively from the terminal:
bin/rails g postnhost:user
The generator prompts for the name, email, password, and password confirmation.
PostnHost ships precompiled CSS and a prebundled JavaScript ES module. The engine serves these files through Rails asset helpers, so the host application does not need to compile PostnHost's JavaScript or install its frontend dependencies.
| Host setup | Compatible | How PostnHost integrates |
|---|---|---|
| Propshaft | ✅ | The engine registers its packaged builds and images with the host asset pipeline. |
| Sprockets | ✅ | The engine registers its packaged builds and images for precompilation. |
importmap-rails | ✅ | The engine bundle is loaded separately from the host import map. No pins are required. |
jsbundling-rails with esbuild | ✅ | The engine uses its packaged bundle and does not need to be added to the host esbuild entry point. |
cssbundling-rails or host Tailwind CSS | ✅ | The packaged engine CSS works independently. Use PostnHost host-tailwind mode only when copied views introduce new Tailwind classes. |
A host application must use Propshaft or Sprockets to serve the packaged engine assets, and either pipeline can be paired with import maps or esbuild.
Postnhost.configure)Edit config/initializers/postnhost.rb:
Postnhost.configure do |config|
# Site defaults used when the matching Dashboard → Settings field is blank
# config.site_url = 'https://example.com'
# config.public_page_size = 12
# config.default_timezone = 'UTC'
# Optional: required only for AI-assisted translations
# config.openai_api_key = 'sk-...'
# config.openai_gpt_model = 'gpt-5.6-luna'
# Required by the generated CarrierWave initializer in production
# config.aws_access_key_id = 'AKIA...'
# config.aws_secret_access_key = '...'
# config.aws_region = 'us-east-1'
# config.aws_bucket = 'my-bucket'
# config.aws_endpoint_url_s3 = 'https://fly.storage.tigris.dev'
end
Site URL, pagination, and timezone values are optional defaults; values saved under Dashboard → Settings take priority. The OpenAI settings are optional and needed only for AI translations.
The generated CarrierWave initializer uses S3-compatible storage in production. Configure the relevant AWS/S3 values above before deploying; aws_endpoint_url_s3 is needed only for providers that use a custom endpoint. Alternatively, edit config/initializers/carrierwave.rb to use a different production storage backend.
Set dashboard overrides for the canonical site URL and public articles per page under Dashboard → Settings → Site. When both site URL sources are blank, public URLs use the incoming request origin.
Instead of defining values in the initializer, you can add them under postnhost: in Rails credentials (bin/rails credentials:edit).
postnhost:
# For AI translations
openai_access_token: ...
openai_gpt_model: gpt-5.6-luna
# For image uploads (S3-compatible)
aws_access_key_id: AKIA...
aws_secret_access_key: ...
aws_region: us-east-1
aws_bucket_name: my-bucket
aws_endpoint_url_s3: https://s3.us-east-1.amazonaws.com # or Tigris/other S3-compatible
PostnHost includes three public templates: Default, Swiss Editorial, and Workspace Journal. Open Template in the CMS, choose a template, preview it, and save the selection.
Copy views to your application for manual/vibecoding customization:
bin/rails g postnhost:views --views-scope=minimal
# or
bin/rails g postnhost:views --views-scope=full
Host-application copies override the packaged engine views. The CMS selection still controls which template is active.
By default, PostnHost serves packaged engine CSS (postnhost/application.css) for zero-setup installs.
If your copied views introduce new Tailwind utility classes, enable host-tailwind mode so those classes are compiled in the host app.
--views-scope=minimal:
app/views/postnhost/public/templates/default/content_for/--views-scope=full:
app/views/postnhost/public/templates/app/views/layouts/postnhost/public/templates/Enable host-tailwind mode in your host app:
bin/rails g postnhost:tailwindcss:install
bin/dev
This generator creates:
app/assets/stylesheets/postnhost/host.tailwind.cssProcfile.dev watcher entry:
postnhost_css: bundle exec rails postnhost:tailwindcss:watchpackage.json scripts (if package.json exists):
postnhost:tailwindcsspostnhost:tailwindcss:watchHost-tailwind mode builds one combined stylesheet from the engine and host view sources at app/assets/builds/postnhost/application.css. That host-owned file transparently overrides the packaged asset with the same logical path; the engine always requests postnhost/application. This avoids competing Tailwind builds and lets copied views use ordinary classes such as bg-red-500 or md:grid.
If the host build is absent, Rails uses the packaged engine asset automatically. Both packaged and combined stylesheets are limited to PostnHost layouts through the data-postnhost root attribute. You do not need to edit layout tags or prefix Tailwind classes. Host customization requires Tailwind CSS 4.
Update the host app public/robots.txt so it points to the correct PostnHost sitemap URL.
PostnHost generates sitemap.xml automatically from live public articles and pages.
If the engine is mounted at /:
Sitemap: https://your-domain.com/sitemap.xml
If the engine is mounted at /blog:
Sitemap: https://your-domain.com/blog/sitemap.xml
Set the canonical production origin under Dashboard → Settings → Site or with config.site_url so sitemap URLs are generated with the correct host.
PostnHost renders favicon tags from app/views/layouts/postnhost/_favicon.html.erb and points to root-level public paths:
/favicon.ico/icon.svg/apple-touch-icon.png/site.webmanifestBecause these are absolute paths, they are served from the host app public/ directory.
Replace the files in your host app:
# From host app root
cp /path/to/your/favicon.ico public/favicon.ico
cp /path/to/your/icon.svg public/icon.svg
cp /path/to/your/apple-touch-icon.png public/apple-touch-icon.png
cp /path/to/your/site.webmanifest public/site.webmanifest
If you need different markup or paths, run either postnhost:views command from the template-customization section, then edit:
app/views/layouts/postnhost/_favicon.html.erbPostnHost ships with locale files for: en, fr, de, ja, ko, pt, pl, es, ru.
You can use PostnHost without extra i18n setup for a single-locale site. If you use multiple locales (localized routes, language switcher, or custom config/locales/*.yml files), add this to config/application.rb:
config.i18n.default_locale = :en
config.i18n.available_locales = %i[en fr de ja ko pt pl es ru]
config.i18n.fallbacks = [:en]
Required: config.i18n.fallbacks = [:en] (or a map that ultimately falls back to a default language).
If you want to add more languages, add the locale code to available_locales and add config/locales/<code>.yml. To scaffold English strings into your host app:
bin/rails g postnhost:locale it
Then translate the values under postnhost.public as needed.
You can define static public pages directly in your host app under:
app/views/postnhost/static_pages/*.html.erbYou can generate starter templates:
bin/rails g postnhost:static_pages terms privacy about
Examples:
app/views/postnhost/static_pages/terms.html.erb → /blog/termsapp/views/postnhost/static_pages/privacy.html.erb → /blog/privacyapp/views/postnhost/static_pages/about.html.erb → /blog/aboutLink helpers:
<%= link_to "Terms", postnhost.public_static_page_path(slug: "terms") %>
The engine mounts at /blog by default. You can change this in config/routes.rb:
mount Postnhost::Engine, at: "/blog"
GET / - Blog indexGET /:locale - Blog index (localized)GET /search - Article searchGET /:locale/search - Article search (localized)GET /authors/:slug - Author show (when author pages are enabled)GET /:locale/authors/:slug - Author show (localized, when author pages are enabled)GET /:category_slug - Category articlesGET /:locale/:category_slug - Category articles (localized)GET /:slug - Article show or static pageGET /:locale/:slug - Article show or static page (localized)GET /preview/:id - Article previewGET /sitemap.xml - XML sitemapGET /sitemap.xsl - Sitemap stylesheetReview these host-application settings before deployment:
public/robots.txt so its sitemap URL matches the production domain and engine mount path.public/404.html and public/500.html pages.For engine development, use Ruby 4.0.6 and Node.js 24+ as listed in mise.toml. Install them with mise, or another version manager, then run:
cd postnhost
bundle install
yarn install
Run both JS and CSS watchers in one command:
yarn dev
From the engine root:
bundle install
yarn install
bundle exec rake prepare_test_db
bundle exec rspec
System specs run with Selenium headless by default. Use visible Chrome when needed:
SYSTEM_TESTS_BROWSER=1 bundle exec rspec spec/system
The gem is available as open source under the terms of the MIT License.
3 commits
JavaScript
56.3%
Ruby
28.1%
HTML
15.1%
Open-source, SEO-ready CMS engine for Rails with structured data, multilingual publishing, version history, and a rich-text editor.
27
stars
3
commits
JavaScript
primary language
Aug 31, 2026
updated
PostnHost is an open source CMS engine built with Rails, Hotwire, and TailwindCSS. Write, translate, and publish articles with a rich text editor, full version history, and proper SEO that works out of the box.
Mount this engine into an existing Rails app, or use the postnhost-app repository for a pre-configured, self-hosted Rails application.

Add PostnHost to your host application's Gemfile:
gem "postnhost"
Run the installer:
bundle install
bin/rails g postnhost:install
This will:
config/initializers/postnhost.rb with configuration optionsconfig/initializers/carrierwave.rb (if not exists)Run migrations:
bin/rails postnhost:install:migrations
bin/rails db:migrate
If there are no users yet, visit /blog/onboarding to create the first CMS administrator with credentials you choose. This is the default installer path; if you mount the engine somewhere else, open <mount-path>/onboarding. The setup flow can add optional sample content after the account is created.
Alternatively, create a CMS user interactively from the terminal:
bin/rails g postnhost:user
The generator prompts for the name, email, password, and password confirmation.
PostnHost ships precompiled CSS and a prebundled JavaScript ES module. The engine serves these files through Rails asset helpers, so the host application does not need to compile PostnHost's JavaScript or install its frontend dependencies.
| Host setup | Compatible | How PostnHost integrates |
|---|---|---|
| Propshaft | ✅ | The engine registers its packaged builds and images with the host asset pipeline. |
| Sprockets | ✅ | The engine registers its packaged builds and images for precompilation. |
importmap-rails | ✅ | The engine bundle is loaded separately from the host import map. No pins are required. |
jsbundling-rails with esbuild | ✅ | The engine uses its packaged bundle and does not need to be added to the host esbuild entry point. |
cssbundling-rails or host Tailwind CSS | ✅ | The packaged engine CSS works independently. Use PostnHost host-tailwind mode only when copied views introduce new Tailwind classes. |
A host application must use Propshaft or Sprockets to serve the packaged engine assets, and either pipeline can be paired with import maps or esbuild.
Postnhost.configure)Edit config/initializers/postnhost.rb:
Postnhost.configure do |config|
# Site defaults used when the matching Dashboard → Settings field is blank
# config.site_url = 'https://example.com'
# config.public_page_size = 12
# config.default_timezone = 'UTC'
# Optional: required only for AI-assisted translations
# config.openai_api_key = 'sk-...'
# config.openai_gpt_model = 'gpt-5.6-luna'
# Required by the generated CarrierWave initializer in production
# config.aws_access_key_id = 'AKIA...'
# config.aws_secret_access_key = '...'
# config.aws_region = 'us-east-1'
# config.aws_bucket = 'my-bucket'
# config.aws_endpoint_url_s3 = 'https://fly.storage.tigris.dev'
end
Site URL, pagination, and timezone values are optional defaults; values saved under Dashboard → Settings take priority. The OpenAI settings are optional and needed only for AI translations.
The generated CarrierWave initializer uses S3-compatible storage in production. Configure the relevant AWS/S3 values above before deploying; aws_endpoint_url_s3 is needed only for providers that use a custom endpoint. Alternatively, edit config/initializers/carrierwave.rb to use a different production storage backend.
Set dashboard overrides for the canonical site URL and public articles per page under Dashboard → Settings → Site. When both site URL sources are blank, public URLs use the incoming request origin.
Instead of defining values in the initializer, you can add them under postnhost: in Rails credentials (bin/rails credentials:edit).
postnhost:
# For AI translations
openai_access_token: ...
openai_gpt_model: gpt-5.6-luna
# For image uploads (S3-compatible)
aws_access_key_id: AKIA...
aws_secret_access_key: ...
aws_region: us-east-1
aws_bucket_name: my-bucket
aws_endpoint_url_s3: https://s3.us-east-1.amazonaws.com # or Tigris/other S3-compatible
PostnHost includes three public templates: Default, Swiss Editorial, and Workspace Journal. Open Template in the CMS, choose a template, preview it, and save the selection.
Copy views to your application for manual/vibecoding customization:
bin/rails g postnhost:views --views-scope=minimal
# or
bin/rails g postnhost:views --views-scope=full
Host-application copies override the packaged engine views. The CMS selection still controls which template is active.
By default, PostnHost serves packaged engine CSS (postnhost/application.css) for zero-setup installs.
If your copied views introduce new Tailwind utility classes, enable host-tailwind mode so those classes are compiled in the host app.
--views-scope=minimal:
app/views/postnhost/public/templates/default/content_for/--views-scope=full:
app/views/postnhost/public/templates/app/views/layouts/postnhost/public/templates/Enable host-tailwind mode in your host app:
bin/rails g postnhost:tailwindcss:install
bin/dev
This generator creates:
app/assets/stylesheets/postnhost/host.tailwind.cssProcfile.dev watcher entry:
postnhost_css: bundle exec rails postnhost:tailwindcss:watchpackage.json scripts (if package.json exists):
postnhost:tailwindcsspostnhost:tailwindcss:watchHost-tailwind mode builds one combined stylesheet from the engine and host view sources at app/assets/builds/postnhost/application.css. That host-owned file transparently overrides the packaged asset with the same logical path; the engine always requests postnhost/application. This avoids competing Tailwind builds and lets copied views use ordinary classes such as bg-red-500 or md:grid.
If the host build is absent, Rails uses the packaged engine asset automatically. Both packaged and combined stylesheets are limited to PostnHost layouts through the data-postnhost root attribute. You do not need to edit layout tags or prefix Tailwind classes. Host customization requires Tailwind CSS 4.
Update the host app public/robots.txt so it points to the correct PostnHost sitemap URL.
PostnHost generates sitemap.xml automatically from live public articles and pages.
If the engine is mounted at /:
Sitemap: https://your-domain.com/sitemap.xml
If the engine is mounted at /blog:
Sitemap: https://your-domain.com/blog/sitemap.xml
Set the canonical production origin under Dashboard → Settings → Site or with config.site_url so sitemap URLs are generated with the correct host.
PostnHost renders favicon tags from app/views/layouts/postnhost/_favicon.html.erb and points to root-level public paths:
/favicon.ico/icon.svg/apple-touch-icon.png/site.webmanifestBecause these are absolute paths, they are served from the host app public/ directory.
Replace the files in your host app:
# From host app root
cp /path/to/your/favicon.ico public/favicon.ico
cp /path/to/your/icon.svg public/icon.svg
cp /path/to/your/apple-touch-icon.png public/apple-touch-icon.png
cp /path/to/your/site.webmanifest public/site.webmanifest
If you need different markup or paths, run either postnhost:views command from the template-customization section, then edit:
app/views/layouts/postnhost/_favicon.html.erbPostnHost ships with locale files for: en, fr, de, ja, ko, pt, pl, es, ru.
You can use PostnHost without extra i18n setup for a single-locale site. If you use multiple locales (localized routes, language switcher, or custom config/locales/*.yml files), add this to config/application.rb:
config.i18n.default_locale = :en
config.i18n.available_locales = %i[en fr de ja ko pt pl es ru]
config.i18n.fallbacks = [:en]
Required: config.i18n.fallbacks = [:en] (or a map that ultimately falls back to a default language).
If you want to add more languages, add the locale code to available_locales and add config/locales/<code>.yml. To scaffold English strings into your host app:
bin/rails g postnhost:locale it
Then translate the values under postnhost.public as needed.
You can define static public pages directly in your host app under:
app/views/postnhost/static_pages/*.html.erbYou can generate starter templates:
bin/rails g postnhost:static_pages terms privacy about
Examples:
app/views/postnhost/static_pages/terms.html.erb → /blog/termsapp/views/postnhost/static_pages/privacy.html.erb → /blog/privacyapp/views/postnhost/static_pages/about.html.erb → /blog/aboutLink helpers:
<%= link_to "Terms", postnhost.public_static_page_path(slug: "terms") %>
The engine mounts at /blog by default. You can change this in config/routes.rb:
mount Postnhost::Engine, at: "/blog"
GET / - Blog indexGET /:locale - Blog index (localized)GET /search - Article searchGET /:locale/search - Article search (localized)GET /authors/:slug - Author show (when author pages are enabled)GET /:locale/authors/:slug - Author show (localized, when author pages are enabled)GET /:category_slug - Category articlesGET /:locale/:category_slug - Category articles (localized)GET /:slug - Article show or static pageGET /:locale/:slug - Article show or static page (localized)GET /preview/:id - Article previewGET /sitemap.xml - XML sitemapGET /sitemap.xsl - Sitemap stylesheetReview these host-application settings before deployment:
public/robots.txt so its sitemap URL matches the production domain and engine mount path.public/404.html and public/500.html pages.For engine development, use Ruby 4.0.6 and Node.js 24+ as listed in mise.toml. Install them with mise, or another version manager, then run:
cd postnhost
bundle install
yarn install
Run both JS and CSS watchers in one command:
yarn dev
From the engine root:
bundle install
yarn install
bundle exec rake prepare_test_db
bundle exec rspec
System specs run with Selenium headless by default. Use visible Chrome when needed:
SYSTEM_TESTS_BROWSER=1 bundle exec rspec spec/system
The gem is available as open source under the terms of the MIT License.
3 commits
JavaScript
56.3%
Ruby
28.1%
HTML
15.1%