not_pressed-core
The core engine behind NotPressed — a drop-in, fully open-source CMS for Rails 8. Auto-discovers your app's navigation, provides a block-based content editor, and supports plugins and themes — all without the WordPress tax.
Not WordPress. Not pressed for cash. Not pressed for time.
Why not_pressed-core?
Most CMS options force you to either adopt a monolithic platform (WordPress, Strapi) or bolt on a headless CMS that doesn't understand your Rails app. not_pressed-core takes a different approach: it's a Rails engine that lives inside your existing application, works with your routes and views, and stays out of your way.
- 3 commands to a working CMS — install the gem, run the generator, migrate
- Works with your app — auto-discovers routes, partials, and navigation from your existing codebase
- Zero external JS — built entirely on Hotwire, Turbo, and Stimulus
- Batteries included — pages, blog, media library, block editor, forms, SEO, themes, plugin system
- Extensible — plugin architecture, custom content types, theme system, import/export
Requirements
- Ruby >= 3.2
- Rails >= 8.0
- Active Storage (for media library)
Installation
Add not_pressed-core to your Gemfile:
gem "not_pressed-core"Run the install generator:
bundle install
rails generate not_pressed:install
rails db:migrateOptionally load sample content:
rails runner "load 'db/seeds/not_pressed.rb'"Visit /not_pressed to access the admin panel.
Features
Block-Based Page Editor
Build pages from ordered, typed content blocks with a drag-and-drop editor:
| Block Type | Description |
|---|---|
text |
Rich text paragraph |
heading |
Section heading (h1-h6) |
image |
Image with caption and alt text |
video |
Embedded video |
code |
Syntax-highlighted code block |
quote |
Blockquote with attribution |
divider |
Visual separator |
html |
Raw HTML injection |
form |
Embedded form (from form builder) |
gallery |
Image grid with lightbox viewer |
callout |
Highlighted callout box (plugin) |
Custom block types can be added via the plugin system.
Page Management
- Hierarchical page tree with drag-to-reorder
- Draft / published / scheduled / archived workflow
- Page duplication, version history
- SEO fields, layout selection, visibility controls
- Live preview with split/toggle pane
Blog
Built-in blog with categories, tags, and RSS:
- Blog posts are pages with the
blog_postcontent type - Category and tag management in admin
- Public blog listing with pagination
- Category and tag filtering
- Year/month archives
- RSS 2.0 feed at
/blog/feed.rss
Media Library
Centralized media management backed by Active Storage:
- Upload, browse, search, and filter
- Automatic metadata extraction (dimensions, file size, content type)
- Alt text and title fields for accessibility and SEO
- Image variants and thumbnail generation
- Media picker modal for the block editor
Forms
Drag-and-drop form builder with submissions:
- Field types: text, email, textarea, select, checkbox, radio, number, date, phone, url
- Submission management with CSV export
- Email notifications on submission
- Embeddable via the
formblock type
SEO & Discoverability
- Per-page meta title, description, Open Graph, Twitter Cards
- Canonical URLs and robots meta directives
- Auto-generated XML sitemap at
/sitemap.xml - Configurable
robots.txt - Per-page and global JS/CSS code injection
Navigation
not_pressed-core builds navigation automatically using three layers:
- Config DSL — explicitly defined menus
- Route introspection — scans your Rails routes for navigable pages
- Partial parsing — detects existing nav/menu/header partials in your views
Configuration
The install generator creates config/initializers/not_pressed.rb:
NotPressed.configure do |config|
# Site name displayed in the admin header
config.site_name = "My Site"
# Admin panel mount path (default: "not_pressed")
config.admin_path = "admin"
# Records per page in admin listings
config.per_page = 25
# Authentication — Devise method, custom Proc, or nil (no auth)
config.admin_authentication = :authenticate_admin_user!
config.admin_current_user = :current_admin_user
config.admin_unauthorized_redirect = "/login"
# Available layouts for pages
config.available_layouts = %w[default sidebar full_width]
# Default theme
config.default_theme = "Starter"
# Auto-discover navigation from routes and view partials
config.auto_discover_navigation = true
config.cache_navigation = true
# SEO title separator
config.seo_title_separator = " | "
# Global code injection (head/body)
config.global_head_code = ""
config.global_body_code = ""
# Navigation DSL
config.menus do |menu|
menu.add "Home", "/"
menu.add "Blog", "/blog"
menu.add "About", "/about"
end
endAuthentication
not_pressed-core doesn't ship its own auth — it plugs into yours:
# Devise
config.admin_authentication = :authenticate_admin_user!
# Custom logic
config.admin_authentication = ->(controller) {
controller.redirect_to "/login" unless controller.session[:admin]
}
# No auth (development only!)
config.admin_authentication = nilExtending NotPressed
Custom Content Types
Define custom content types with the builder DSL:
NotPressed.define_content_type(:event) do
label "Event"
icon "calendar"
description "An event with date and location"
allowed_blocks :text, :heading, :image
has_slug true
has_parent false
field :event_date, :datetime, label: "Event Date", required: true
field :venue, :string, label: "Venue"
field :ticket_url, :url, label: "Ticket URL"
endPlugins
WordPress-style plugin system with hooks, filters, custom blocks, and admin extensions:
class MyPlugin < NotPressed::Plugin
name "my_plugin"
version "1.0.0"
description "Does something useful"
on(:after_page_save) { |page| Rails.logger.info "Saved: #{page.title}" }
block_type :callout
settings do
field :api_key, :string, label: "API Key"
end
admin_menu label: "My Plugin", icon: "puzzle-piece"
routes do
get "my-plugin", to: "my_plugin#index"
end
endPlugins can be exported as .zip archives and imported on other installations via the admin panel.
See Plugin Development Guide for full documentation.
Themes
Swappable themes with layouts, color schemes, and template overrides:
class MyTheme < NotPressed::Theme
name "my_theme"
version "1.0.0"
layout :default, label: "Default", primary: true
layout :sidebar, label: "With Sidebar"
stylesheet "not_pressed/themes/my_theme"
color_scheme do
color :primary, "#3366cc", label: "Primary"
color :background, "#ffffff", label: "Background"
color :text, "#333333", label: "Text"
end
endThemes support color customization from the admin panel, template overrides via view path prepending, and can be exported/imported as .zip archives.
See Theme Development Guide for full documentation.
Development
After checking out the repo:
bundle install
cd spec/dummy && rails db:create db:migrate && cd ../..
bundle exec rspecThe test suite uses a Rails 8 dummy app in spec/dummy/.
Documentation
- Plugin Development Guide
- Theme Development Guide
- Hooks & Filters Reference
- CSS Variables Reference
- API Reference
License
not_pressed-core is released under the NPL-1.0 License.