--- URL: "/" LLMS_URL: "/index.md" layout: "home" hero: name: "Alchemy" image: src: "/icon.svg" alt: "AlchemyCMS Icon" text: "The Rails CMS framework" tagline: "For teams that need full control" actions: - text: "Read the Guides →" link: "getting_started" theme: "brand" - text: "About Alchemy" link: "about" theme: "alt" features: - title: "Content Framework" icon: "🧩" details: "Compose pages from reusable, semantic content components with strict content and markup separation." - title: "Open Source" icon: "❤️" details: "Free forever. Community-driven since 2010. No vendor lock-in. Integrates into any Rails application." - title: "Built for Teams" icon: "🎉" details: "A beautiful, intuitive admin interface that content authors love. Editors work within the structure you define, without breaking the layout." ---

Sponsored by

--- URL: "/about.html" LLMS_URL: "/about.md" prev: false next: text: "Getting Started" link: "getting_started" --- # About AlchemyCMS Alchemy is a flexible, developer-friendly content management framework for Ruby on Rails. It gives you full control over markup and styling while providing editors with a powerful, intuitive admin interface. ## Rails Engine Alchemy is a [Rails engine](https://guides.rubyonrails.org/engines.html) that integrates into your existing application. It uses standard Rails conventions — ERB templates, ActiveRecord, asset pipeline — so there is nothing new to learn. ## Content Architecture Unlike CMS systems that store complete HTML pages, Alchemy stores only structured content: text, references to images and attachments, and richtext. No HTML markup, no CSS, no layout. You control how content is rendered. ### The Content Hierarchy Content Hierarchy Content Hierarchy - A **Site** has one or more **Languages**. - Each language has a tree of **[Pages](pages)**. - Each page maintains a **draft version** for editing and a **public version** for visitors. When a page is published, Alchemy duplicates the draft's elements into the public version. This is handled transparently. - Pages support **[time-based publishing](pages#publishing)**, allowing scheduled publishing and expiration. Since 8.1, [elements support this as well](elements#time-based-publishing). - Pages contain **[Elements](elements)** — reusable content components like articles, hero sections, or cards. Elements can be nested. - Elements contain **[Ingredients](ingredients)** — typed content fields like Text, Richtext, Picture, or Boolean. ### The Render Flow Alchemy uses standard [Rails partials](https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials) and [ViewComponents](https://viewcomponent.org). It has no custom templating language. When Alchemy renders a page, these files are used: 1. `app/views/layouts/application.html.erb` — your application layout 2. `app/views/alchemy/page_layouts/_.html.erb` — the page layout partial 3. `app/views/alchemy/elements/_.html.erb` — the element partial 4. Ingredient view components render the individual content fields ### Planning Your Content Structure Start by identifying the different types of pages your site needs (called page layouts). Every structurally different page should have its own layout. [More about pages.](pages) Then split the content within each layout into elements. Group related content fields into reusable components. Elements are the key building blocks of Alchemy. [More about elements.](elements) Finally, define the ingredients for each element — the individual content fields like headlines, images, and text. [More about ingredients.](ingredients) ## Image Processing Alchemy renders images on the fly using [ImageMagick](https://imagemagick.org) or [libvips](https://www.libvips.org). Images are stored as originals in the picture library. Editors assign them to elements and can crop them using the built-in cropping tool. You define the rendering dimensions and output format. [More about rendering images.](render_images) ## Caching Alchemy uses Rails' Russian doll caching for fast content delivery. With every page request Alchemy sends `Cache-Control` headers that CDNs, proxies and browsers use to cache the page. Caching can be configured globally or per page layout. [More about caching.](configuration#caching) ## Admin Modules Alchemy comes with these admin modules: - **[Pages](pages)** — manage the page tree and edit content - **[Menus](menus)** — manage navigation trees independently from pages - **[Languages](languages)** — multi-language support - **[Sites](sites)** — multi-site support - **[Library](library)** — picture and attachment management - **[Tags](tags)** — tagging for pictures, attachments, and pages - **Users** — user management (with [alchemy-devise](extensions#alchemy-devise)) ::: tip You can extend the admin with your own [custom modules](how_to_create_modules). See [Extensions](extensions) for a full list of available add-ons. ::: --- URL: "/configuration.html" LLMS_URL: "/configuration.md" prev: text: "Rendering Images" link: "render_images" next: text: "Deployment" link: "deployment" --- # Configuration Alchemy is configured through a Ruby initializer at `config/initializers/alchemy.rb`. The install generator creates this file for you. All settings have sensible defaults — you only need to configure what you want to change. ### The Initializer ~~~ ruby # config/initializers/alchemy.rb Alchemy.configure do |config| config.output_image_quality = 85 config.storage_adapter = "active_storage" end ~~~ ::: info NOTE `Alchemy.configure` is available since 8.1. In 8.0, use `Alchemy.config.tap` instead. ::: ### Environment-Specific Configuration Use Rails' standard environment files to override settings per environment. ~~~ ruby # config/environments/production.rb Rails.application.configure do Alchemy.config.cache_pages = true end ~~~ ## Default Site and Language Used by the built-in onboarding to create the initial site, language, and frontpage. ~~~ ruby config.default_site.tap do |default_site| default_site.name = "Default Site" default_site.host = "*" end config.default_language.tap do |default_language| default_language.name = "English" default_language.code = "en" default_language.page_layout = "index" default_language.frontpage_name = "Index" end ~~~ ## Images See the [Rendering Images guide](render_images) for details on these settings. ### output_image_quality `Integer` (Default: `85`) Quality for rendered JPEG and WebP images. ### preprocess_image_resize `String` (Default: `nil`) Downsize images on upload. Example: `"1000x1000"`. ### image_output_format `String` (Default: `"original"`) Global output format. `"original"`, `"jpg"`, `"png"`, or `"webp"`. ### sharpen_images `Boolean` (Default: `false`) Enable image sharpening on rendered variants. ### storage_adapter `String` The storage backend for pictures and attachments. Either `"active_storage"` or `"dragonfly"` (the default). Can also be set via the `ALCHEMY_STORAGE_ADAPTER` environment variable. ## Caching ### cache_pages `Boolean` (Default: `true`) Enable or disable page caching globally. You can also [control caching per page layout](pages#caching). ### page_cache Controls the `Cache-Control` headers for cached pages. ~~~ ruby config.page_cache.tap do |page_cache| page_cache.max_age = 600 # max-age in seconds (default: 600) page_cache.stale_while_revalidate = 3600 # stale-while-revalidate in seconds (default: nil) end ~~~ :::tip NOTE When `stale_while_revalidate` is not set, Alchemy sends `must-revalidate` in the `Cache-Control` header instead. ::: ## Uploads Configure upload limits and allowed file types. ~~~ ruby config.uploader.tap do |uploader| uploader.upload_limit = 50 uploader.file_size_limit = 100 uploader.allowed_filetypes.tap do |file_types| file_types.alchemy_attachments = ["pdf", "zip"] # default: ["*"] (all types) file_types.alchemy_pictures = ["jpg", "jpeg", "gif", "png", "svg", "webp"] # default end end ~~~ ## Links ### link_target_options `Array` (Default: `["blank"]`) Values for the link target select in the link dialog. Each value is stored with a leading underscore and rendered as the link's `target` attribute, so `blank` becomes `target="_blank"`. If you render a link ingredient in your own view, pass the stored value through `Alchemy::Ingredients::LinkTarget#link_target_value` instead of comparing it yourself. It also maps the legacy `blank` value, stored before the underscore was added. ### link_dialog_tabs Extend the link dialog with custom tabs. The default tabs are: Internal, Anchor, External, and File. ~~~ ruby config.link_dialog_tabs << "Acme::LinkTab" ~~~ ::: tip [alchemy-solidus](extensions#alchemy-solidus) provides a tab for linking to Solidus products. ::: ## Mailer Configure Alchemy's built-in contact form mailer. See the [contact form guide](how_to_create_a_contact_form) for a full walkthrough. ~~~ ruby config.mailer.tap do |mailer| mailer.page_layout_name = "contact" mailer.forward_to_page = false mailer.mail_success_page = "thanks" mailer.mail_from = "contact@example.com" mailer.mail_to = "admin@example.com" mailer.subject = "New contact form message" mailer.fields = ["name", "email", "message"] mailer.validate_fields = ["name", "email"] end ~~~ ## Sitemap Alchemy generates a Google-compatible XML sitemap at `/sitemap.xml`. ~~~ ruby config.sitemap.tap do |sitemap| sitemap.show_root = true sitemap.show_flag = false end ~~~ Set `show_flag` to `true` to add a "Show in sitemap" checkbox to the page settings, allowing editors to control sitemap visibility per page. ## Publishing ### publish_targets Trigger jobs when pages are published. For example, to notify a CDN or rebuild a static cache. Each target must be an `ActiveJob` class that accepts a page as argument. ~~~ ruby config.publish_targets << PublishToCdnJob ~~~ ## Admin UI ### admin_page_preview_layout `String` (Default: `"application"`) The layout used for rendering the page preview in the admin. ### page_preview_sizes `Array` (Default: `[360, 640, 768, 1024, 1280, 1440]`) The viewport sizes available in the preview size selector. ### show_page_searchable_checkbox `Boolean` (Default: `false`) Show a searchable checkbox in the page form. Useful with search plugins like [alchemy-pg_search](extensions#alchemy-pg_search). ### auto_logout_time `Integer` (Default: `30`) Minutes of inactivity before the admin session expires. Used by [alchemy-devise](extensions#alchemy-devise) to configure Devise's `:timeoutable` module. Disabled in development. ### items_per_page `Integer` (Default: `15`) Number of items per page in admin list views (uses Kaminari). ### menu `Array` The entries of the admin main navigation. Alchemy's own entries (Dashboard, Pages, Menus, Languages, Sites, Tags and Library) are the default. Add entries for your own modules with `config.menu.add`: ~~~ ruby config.menu.add( name: "events", title: "Events", icon: "calendar", url: -> { main_app.admin_events_path }, condition: -> { can?(:index, :admin_events) } ) ~~~ See [Creating Modules](how_to_create_modules#menu-entry) for all options. ## Admin JavaScript and CSS Alchemy uses [importmap-rails](https://github.com/rails/importmap-rails) for JavaScript modules in the admin. You can extend the admin interface with custom modules and stylesheets. ~~~ ruby # Pin a JS module and import it in the admin Alchemy.importmap.pin "flatpickr/de", to: "https://ga.jspm.io/npm:flatpickr@4.6.13/dist/l10n/de.js" config.admin_js_imports << "flatpickr/de" # Add a custom stylesheet config.admin_stylesheets.add("my_app/admin_extension") ~~~ ## Admin Path Change the admin URL path or restrict access: ~~~ ruby # config/initializers/alchemy.rb Alchemy.admin_path = "backend" Alchemy.admin_constraints = { subdomain: "admin" } ~~~ The default admin path is `/admin`. ## User Roles ### user_roles `Array` (Default: `["member", "author", "editor", "admin"]`) Available user roles. Each role inherits the permissions of the previous one. See the [custom authentication guide](how_to_add_custom_authentication) for setting up your user model. - **member** - Can view published pages and attachments, including restricted ones - **author** - Can edit page content, manage elements and ingredients, browse the library - **editor** - Can create, delete, publish and reorder pages, manage pictures, attachments and tags - **admin** - Can manage languages, sites and system settings You can also restrict which roles can edit specific page layouts with [`editable_by`](pages#editable_by). Alchemy uses [CanCanCan](https://github.com/CanCanCommunity/cancancan) for authorization. You can register custom abilities. ~~~ ruby config.abilities << "MyCustom::Ability" ~~~ Roles can be translated in your locale files. See the [I18n guide](i18n) for details. ~~~ yaml # config/locales/en.yml en: alchemy: user_roles: member: Member author: Author ~~~ ## Preview Alchemy offers two ways to customize the admin's page preview. ### Preview Sources When multiple preview sources are configured, a select menu appears in the admin preview frame. This is useful when you want to preview content across different frontends, for example if you use Alchemy as a headless CMS via [alchemy-json_api](extensions#alchemy-json_api): ~~~ ruby config.preview_sources << "MyCustom::PreviewSource" ~~~ ### Preview Host To replace Alchemy's built-in server-side rendered preview entirely, configure a static preview host. This points the preview frame to an external URL, optionally with Basic Auth: ~~~ ruby config.preview.tap do |preview| preview.host = "https://www.my-static-site.com" preview.auth.tap do |auth| auth.username = ENV["BASIC_AUTH_USERNAME"] auth.password = ENV["BASIC_AUTH_PASSWORD"] end end ~~~ ## Page URL Paths Customize how page URLs are generated: ~~~ ruby # config/initializers/alchemy.rb Alchemy::Page.url_path_class = MyCustomUrlPath ~~~ The default `Alchemy::Page::UrlPath` builds URLs from the page's `urlname` and its ancestors. Your class must respond to `call` and return a path string. ## Format Matchers Named aliases for regular expressions, commonly used for [ingredient validations](elements#validations). ~~~ ruby # Use in your models validates_format_of :url, with: Alchemy.config.format_matchers.url ~~~ ## Error Tracking Replace Alchemy's default error logger with your own handler: ~~~ ruby # config/initializers/alchemy.rb Alchemy::ErrorTracking.notification_handler = MyErrorHandler ~~~ Your class must respond to `call` with an exception argument. ::: tip Ready-made integrations exist for the most common services: [Sentry](extensions#alchemy-sentry), [Bugsnag](extensions#alchemy-bugsnag), [AppSignal](extensions#alchemy-appsignal), and [Airbrake](https://github.com/AlchemyCMS/alchemy-airbrake). See [Extensions](extensions) for more. ::: ## Update Check ### update_check_service `Symbol` (Default: `:alchemy_app`) The service used to check for new Alchemy versions in the admin dashboard. Available options: - `:alchemy_app` — Alchemy's own update check endpoint. Shares minimal data about your installation (Alchemy version, Rails version, Ruby version, origin host) with maintainers of AlchemyCMS - `:ruby_gems` — RubyGems.org API. Recommended if you don't want to share any data with AlchemyCMS maintainers - `:none` — Disables update checks ### update_check_cache_duration `Integer` (Default: `1`) How long (in hours) the update check result is cached. --- URL: "/deployment.html" LLMS_URL: "/deployment.md" prev: text: "Configuration" link: "configuration" next: text: "Updating" link: "upgrading" --- # Deployment Alchemy is a standard Rails application. It works on any platform that runs Rails: Heroku, Render.com, Fly.io, DigitalOcean App Platform, and others. If you can deploy a Rails app, you can deploy Alchemy. This guide covers the production concerns specific to running AlchemyCMS. The key concern on platforms with ephemeral filesystems is [remote storage](#remote-storage) for uploaded files. For general Rails deployment guidance, see [Deploying to Production](https://guides.rubyonrails.org/getting_started.html#deploying-to-production) in the official Rails guides. For a step-by-step example, see the [Deploy with Kamal](how_to_deploy_with_kamal) guide. ## Production Checklist Before deploying, make sure you have: - An image processing library installed (ImageMagick or libvips) - Remote storage configured for pictures and attachments (on ephemeral filesystems) - A database (PostgreSQL, MySQL, or SQLite) ## Image Processing Alchemy requires an image processing library to be installed on your production server for cropping and resizing images. - **ActiveStorage** supports both **libvips** (recommended, faster and lower memory) and **ImageMagick** - **Dragonfly** only supports **ImageMagick** Most deployment targets (Docker images, PaaS platforms) need you to explicitly install one of these. For example, in a Debian-based Dockerfile: ~~~dockerfile # libvips (ActiveStorage only) RUN apt-get install --no-install-recommends -y libvips # or ImageMagick (required for Dragonfly, also works with ActiveStorage) RUN apt-get install --no-install-recommends -y imagemagick ~~~ See the [Rendering Images](render_images) guide for details on configuring the image processor. ## Remote Storage On platforms with ephemeral filesystems (Docker containers, Heroku, Fly.io, Render.com), you must configure remote storage for both **pictures** and **attachments**. Without it, uploaded files will be lost on every deploy. **With ActiveStorage**, configure a remote service in `config/storage.yml`: ~~~yaml amazon: service: S3 bucket: your-bucket region: us-east-1 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> ~~~ Then set it as the active service in `config/environments/production.rb`: ~~~ruby config.active_storage.service = :amazon ~~~ **With Dragonfly**, use the [alchemy-dragonfly-s3](extensions#alchemy-dragonfly-s3) gem. See the [Rendering Images](render_images#remote-storage) guide for the full setup. ::: tip See [Extensions](extensions) for all available storage add-ons. ::: ## Database Alchemy uses standard Rails migrations. Use `db:prepare` for every deploy: ~~~bash bin/rails db:prepare ~~~ This creates the database if it doesn't exist yet, and runs any pending migrations. If you use Alchemy's page seeding feature, add the seeder to your `db/seeds.rb`: ~~~ruby Alchemy::Seeder.seed! ~~~ :::tip NOTE This is optional. Even without it, Alchemy bootstraps the default site, language, and root page on first sign-in. ::: ## Caching Alchemy sets `Cache-Control` headers on published pages. This works with any CDN or reverse proxy (Cloudflare, CloudFront, Nginx, etc.) without additional configuration. For efficient CDN caching, consider setting `stale_while_revalidate`: ~~~ruby Alchemy.config.page_cache.stale_while_revalidate = 3600 ~~~ :::tip INFO This tells CDNs to serve stale content while fetching a fresh version in the background, reducing latency for visitors. ::: See the [Configuration](configuration#caching) guide for more cache settings. --- URL: "/elements.html" LLMS_URL: "/elements.md" prev: text: "Getting Started" link: "getting_started" next: text: "Ingredients" link: "ingredients" --- # Elements Elements are the central building blocks of an Alchemy website. They are reusable content containers - for example, a headline, an image and a text block might form an "article" element. These individual pieces of content within an element are called [ingredients](ingredients). You define elements and their ingredients in YAML. Editors place them on pages through the admin interface. You control how they are rendered with view partials. See the [About guide](about) for how elements fit into the full content architecture. ## Defining elements Elements are defined in the `config/alchemy/elements.yml` file. Element definitions are written in [YAML](http://yaml.org). If this file does not exist yet, create it with the scaffold generator: ~~~ bash bin/rails g alchemy:install ~~~ The generator also creates the other basic folders and files for setting up your website with Alchemy. ### Example Element Definition ~~~ yaml # config/alchemy/elements.yml - name: article unique: true ingredients: - role: image type: Picture - role: headline type: Text as_element_title: true - role: copy type: Richtext ~~~ The element in this example is named **"article"** and can be placed only once per page (because it is `unique`). It has three ingredients of the following types: * [Picture](ingredients#picture) * [Text](ingredients#text) * [Richtext](ingredients#richtext) ::: tip By default, the first ingredient's value is used as the preview text in the element's title bar in the admin frontend. If you want a different ingredient to be used instead, add `as_element_title: true` to that ingredient. In the example above, the **"headline"** ingredient would be used instead of **"image"**. ::: ## Element Settings The following settings can be used to define elements in the `elements.yml`. ### name `String` _required_ A lowercased **unique** name of the element. Separate words need to be underscored. The name is used in the `page_layouts.yml` file to define on which pages the element can be used. It is also the file name of the element's view partial in `app/views/alchemy/elements/`. The name is [translatable](i18n#element-names) for the admin interface. ### unique `Boolean` (Default: `false`) Passing `true` means this element can be placed only once on a page. For more fine-grained control, use [amount](#amount) instead. ### hint `String` A hint for the user in the admin interface that describes what the element is used for. Set to `true` to use a [translated hint](i18n#hints) from your locale files. The hint is displayed as a small question mark icon that reveals a tooltip on hover. ### message `String` A prominent informational message displayed at the top of the element editor form in the admin interface. You can use simple HTML to add emphasis. ### warning `String` A prominent warning message displayed at the top of the element editor form in the admin interface. You can use simple HTML to add emphasis. ### nestable_elements `Array` A collection of element names that can be nested into the element. ### taggable `Boolean` (Default: `false`) Enables the element to be taggable by the user in the admin frontend. ### fixed `Boolean` (Default: `false`) Separates an element from the normal flow. When `true`, the element is only rendered when explicitly requested via the `fixed_elements` scope. See [fixed elements](#rendering-fixed-elements) for more details. ### icon `String|Boolean` Controls the icon in the admin UI. Set to `true` to use `.svg` or set to a string to use `.svg`, both from `app/assets/images/alchemy/element_icons/` in your app. If not set, Alchemy uses its own default icon. ::: tip Alchemy uses Remix Icons throughout its admin interface. To keep your custom element icons consistent, download SVG icons from the [Remix Icon website](https://remixicon.com) and place them in the `element_icons` folder. ::: ### amount `Integer` Maximum number of top-level instances of this element per page. Once the limit is reached, the element is no longer offered in the "add element" dialog. All elements on the draft version are counted regardless of their published state. Defaults to unlimited. Does not apply to nested elements. For a single instance, use [unique](#unique) instead. ~~~ yaml - name: hero_banner amount: 3 ~~~ ### compact `Boolean` (Default: `false`) Renders the element in a compact UI in the admin editor. Useful for elements that are primarily used as nestable children, like slides in a slider, cards in a card grid, or items in a gallery. ~~~ yaml - name: slide compact: true ingredients: - role: image type: Picture ~~~ ### searchable `Boolean` (Default: `true`) Include this element's ingredients in the fulltext search index. Set to `false` for elements that contain sensitive data or purely controlling values like CSS class names, accordion titles, or slider timing configurations. ### deprecated `Boolean|String` (Default: `false`) Mark this element as deprecated. Set to `true` to use a [translated deprecation notice](i18n#deprecation-notices), or provide a custom message string directly. ~~~ yaml - name: old_element deprecated: true - name: legacy_widget deprecated: "Use the new_widget element instead." ~~~ When set to `true`, Alchemy looks up the notice via I18n: ~~~ yaml # config/locales/en.yml en: alchemy: element_deprecation_notices: old_element: "This element is outdated. Please use new_element instead." ~~~ ### ingredients `Array` A collection of ingredients the element contains. An ingredient has to have a `role` (unique per element) and a `type`. ::: tip Have a look at the [ingredients guide](ingredients) to get more information about available ingredient types. ::: ## Available Elements Before you can use elements on pages, you need to define which page layouts they can be placed on. Open `config/alchemy/page_layouts.yml` and add the element name to the list of available elements for a page layout. ~~~ yaml - name: standard elements: [article] autogenerate: [article] ~~~ You can now place the article element on any page with the `standard` page layout. Any new pages created with the `standard` layout will automatically have the article element. ## Nestable Elements Elements can be nested inside other elements. Define the allowed children in your `elements.yml` file. ### Example ~~~ yaml - name: article ingredients: - role: headline type: Text nestable_elements: - text - picture - name: text ingredients: - role: text type: Richtext - name: picture ingredients: - role: text type: Picture ~~~ ::: tip Nested elements can also have `nestable_elements`. Just don't get too crazy with it, though. ::: ### Rendering Nested Elements Use the `render` helper to render all nested elements as a collection. ~~~ erb <%= element_view_for(article) do |el| %>

<%= el.render :headline %>

<%= render article.nested_elements %>
<% end %> ~~~ You can also filter nested elements by name. ~~~ erb <%= element_view_for(article) do |el| %>

<%= el.render :headline %>

<%= render article.nested_elements.named(:text) %>
<%= render article.nested_elements.named(:picture) %>
<% end %> ~~~ Or render a single nested element. ~~~ erb <%= element_view_for(article) do |el| %>

<%= el.render :headline %>

<%= render article.nested_elements.find_by(name: :picture) %>
<%= render article.nested_elements.where(name: :text) %>
<% end %> ~~~ ## Ingredient Groups Ingredients can be visually grouped in the admin editor using the `group` property on ingredient definitions. ~~~ yaml - name: product ingredients: - role: title type: Text - role: description type: Richtext - role: css_class type: Select group: settings - role: width type: Text group: settings ~~~ Grouped ingredients are rendered as collapsible sections in the element editor. Ingredients without a `group` appear ungrouped at the top. ::: tip Use ingredient groups sparingly. Editors should see all content-related ingredients at once without having to expand sections. Groups are best suited for configuration or secondary options that are not part of the main content, such as CSS classes, display settings, or layout options. ::: Group names can be [translated via I18n](i18n#ingredient-groups). ~~~ yaml # config/locales/en.yml en: alchemy: element_groups: settings: Settings product: settings: Display Options ~~~ ## Tagging Elements are taggable. To enable it, add `taggable: true` to the element's definition. ~~~ yaml - name: article taggable: true ingredients: - role: image type: Picture - role: headline type: Text as_element_title: true - role: copy type: Richtext ~~~ Tags are a collection on the `element` object. `element.tag_list` returns an array of tag names. ~~~ erb <%= element.tag_list.join(', ') %> ~~~ Alchemy uses the [gutentag](https://github.com/pat/gutentag) gem, so please refer to the github [README](https://github.com/pat/gutentag/blob/master/README.md) or the [Wiki](https://github.com/pat/gutentag/wiki) for further information. ## Publishing Only published elements are rendered on the public page version. Editors can toggle element visibility in the admin. ### Time-Based Publishing Beginning with Alchemy 8.1, elements support time-based publishing, just like [pages](pages#publishing). Each element has `public_on` and `public_until` timestamps that control when it is visible to visitors. - **`public_on`** — the date and time the element becomes visible (defaults to the current time when created) - **`public_until`** — the date and time the element is automatically hidden ::: tip This is useful for time-limited content like banners, seasonal promotions, or event announcements that should appear and disappear automatically. ::: ## Validations You can enable validations for your ingredients. They behave like the Rails model validations. Supported validations are: * `presence` * `uniqueness` * `format` The `format` validator needs to have a [regular expression](http://rubular.com) or a predefined matcher string as its value. Predefined format matchers are configured in your initializer. You can also add your own. See the [Configuration guide](configuration#format-matchers) for details. ### Format Matchers ~~~ ruby # config/initializers/alchemy.rb Alchemy.configure do |config| config.format_matchers.tap do |format| format.email = /\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/ format.url = /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix end end ~~~ ### Example ~~~ yaml - name: person ingredients: - role: name type: Text validate: - presence: true - role: email type: Text validate: - format: email - role: homepage type: Text validate: - format: !ruby/regexp '/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/'] ~~~ The `email` ingredient gets validated against the predefined `email` matcher from the initializer. The `homepage` ingredient is matched against the given regexp. ### Multiple Validations Ingredients can have multiple validations. ~~~ yaml - name: person ingredients: - role: name type: Text validate: - presence: true - uniqueness: true - format: name ~~~ Validations are evaluated in the order they are defined. The name ingredient will be validated for `presence` first, then for `uniqueness`, and finally against its `format`. ## Rendering ### Generating Partials Each element has a view partial (a [Rails partial](https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials)) that lives in `app/views/alchemy/elements/`. You don't need to create these files yourself. Use the Rails generator to create them. ~~~ bash bin/rails g alchemy:elements --skip ~~~ The `--skip` flag skips files that already exist. The `--force` flag will overwrite your element view partials automatically. ::: tip You can pass `--template-engine` or `-e` as an argument to use `haml`, `slim` or `erb`. The default template engine depends on your settings in your Rails host app. ::: The generator will create partials for each element in your `app/views/alchemy/elements` folder. For the article element from the example above, the generator creates `_article.html.erb`. The generated code serves as a starting point that you can customize to fit your needs. ### Page Layouts With the article element associated with the 'standard' page layout, it can be rendered in the layout partial `app/views/alchemy/page_layouts/_standard.html.erb`. ~~~ erb ...
<%= render_elements %>
... ~~~ This renders all elements from the current page. ::: tip Pages must be published for elements to be rendered. If you aren't seeing content you created in the admin interface, make sure elements are saved and you click the "Publish current page content" button from the edit page admin view. ::: #### Render Only Specific Elements Sometimes you only want to render specific elements on a specific page and maybe exclude some elements. ~~~ erb
<%= render_elements only: 'header' %>
<%= render_elements except: %w(header footer) %>
<%= render_elements only: 'footer' %>
~~~ #### render_elements Options The `render_elements` helper accepts several options: * **only** - Render only elements with these names * **except** - Render all elements except these names * **from_page** - Render elements from a different page. Accepts a page layout name as a `String`, an `Array` of page layout names, or an `Alchemy::Page` instance * **count** - Limit the number of rendered elements * **offset** - Skip the first N elements * **random** - Randomize the order of elements * **fixed** - When `true`, render only fixed elements. See [rendering fixed elements](#rendering-fixed-elements) #### Render Elements from Other Pages A common use case is to have global pages for header and footer parts. ~~~ yaml # config/alchemy/elements.yml - name: header hint: Navigation bar at the top of every page ingredients: # ... - name: footer hint: Footer section at the bottom of every page ingredients: # ... # config/alchemy/page_layouts.yml - name: header unique: true elements: [header] autogenerate: [header] layoutpage: true - name: footer unique: true elements: [footer] autogenerate: [footer] layoutpage: true ~~~ These can be rendered in your `application.html.erb` file. ~~~ erb ...
<%= render_elements from_page: 'header' %>
<%= yield %>
<%= render_elements from_page: 'footer' %>
<%= render "alchemy/edit_mode" %> ~~~ #### Rendering Fixed Elements Often you have a separate section on one page (like a sidebar) or a global section to be rendered on every page (like a navbar or footer). If you configure those elements as `fixed: true` in `elements.yml`, then they'll be separated from the general collection of elements and will be displayed in a separate tab in the admin elements section. ~~~ yaml {3} - name: sidebar unique: true fixed: true ingredients: - role: name type: Text # ... ~~~ You can render fixed elements using `render_elements` with the `fixed` option. ~~~ erb ~~~ You can also access them directly via the page. ~~~ erb <%= render @page.fixed_elements.named('sidebar') %> ~~~ ### Element Views The Alchemy element generator creates a basic view partial for each element. This is where you control how the element's content is rendered on your website. ~~~ erb <%= element_view_for(element) do |el| %>

<%= el.render :headline %>

<%= el.render :image %>

<%= el.render :text %>

<% end %> ~~~ The `element_view_for` helper wraps the content in a `div` by default. You can change the wrapping tag with the `tag` argument, or pass `false` to disable wrapping entirely. #### Block Helper Methods Besides `el.render`, the block helper provides additional methods for accessing ingredient data. ~~~ erb <%= element_view_for(element) do |el| %> <%= el.render :headline %> <% if el.has?(:image) %> <%= el.render :image %> <% end %> <%= el.value(:date) %> <% end %> ~~~ * **`el.render :role`** - Renders the ingredient's view component (the standard way to display ingredients) * **`el.value(:role)`** - Returns the raw value without any view component wrapping * **`el.has?(:role)`** - Returns `true` if the ingredient has a non-blank value * **`el.ingredient_by_role(:role)`** - Returns the ingredient record directly Use `el.ingredient_by_role` when you need to access ingredient attributes beyond the plain value. For example, a Page ingredient has a `page` method that returns the actual `Alchemy::Page` record, which you can use to build custom links. ~~~ erb <%= element_view_for(element) do |el| %> <% page_ingredient = el.ingredient_by_role(:linked_page) %> <% if page_ingredient&.page %> <%= link_to page_ingredient.page.name, show_alchemy_page_path(page_ingredient.page) %> <% end %> <% end %> ~~~ #### Pass Options to the Element Wrapper You can pass additional arguments to add or change any html attributes of the wrapper. ~~~ erb <%= element_view_for(element, tag: 'li', class: 'red', id: 'my_unique_id') do |el| %> ... <% end %> ~~~ #### Pass Options to the Ingredient View You can pass options to the ingredient view. ~~~ erb <%= element_view_for(element) do |el| %> <%= el.render :image, {size: '200x300'}, class: 'image-large' %> <% end %> ~~~ ::: tip Instead of passing the `size` of an image inline, you should consider using static [ingredient settings](ingredients#individual-ingredient-settings) instead. ::: The first hash is the `options`, the second is the `html_options` passed to the ingredient's wrapper. If you only want to pass `html_options`, you need to pass an empty hash as the first argument. ~~~ erb <%= element_view_for(element) do |el| %> <%= el.render :image, {}, class: 'image-large' %> <% end %> ~~~ ## Translating Elements Element and ingredient names are passed through Rails' [I18n](http://guides.rubyonrails.org/i18n.html) library. You can translate them in your `config/locales` language yml file. ~~~ yaml de: alchemy: element_names: contact_form: Kontaktformular search: Suche ingredient_roles: headline: Überschrift ~~~ Ingredient roles can also be translated per element. This is useful when the same role name needs different labels depending on the element. ~~~ yaml de: alchemy: element_names: contact_form: Kontaktformular ingredient_roles: color: Farbe contact_form: color: Button Farbe ~~~ ::: tip See the [I18n guide](i18n) for all available translation scopes including hints, deprecation notices, and ingredient groups. ::: --- URL: "/extensions.html" LLMS_URL: "/extensions.md" --- # Extensions AlchemyCMS has a rich ecosystem of official and community extensions that add functionality to your application. Official extensions are maintained under the [AlchemyCMS GitHub organization](https://github.com/AlchemyCMS). ## Authentication ### alchemy-devise Devise-based user authentication for AlchemyCMS. If you don't bring your own authentication, you can use this gem to authorize users in the Alchemy admin. ## E-commerce ### alchemy-solidus Integrates [Solidus](https://solidus.io) e-commerce with AlchemyCMS. Provides shared admin navigation between both backends, ingredients for placing Solidus products, variants, and taxons on Alchemy pages, and shared admin sessions and user abilities. ## API ### alchemy-json_api A [JSON:API](https://jsonapi.org) compliant API for AlchemyCMS. Use Alchemy as a headless CMS and consume content from any frontend framework. ### alchemy-graphql [GraphQL](https://graphql.org) API for AlchemyCMS. ## Search ### alchemy-pg_search PostgreSQL full text search for AlchemyCMS. Adds search functionality using PostgreSQL's built-in full text search capabilities. ## Storage ### alchemy-dragonfly-s3 Store Alchemy pictures and file attachments on Amazon S3 using the [Dragonfly](https://markevans.github.io/dragonfly/) S3 datastore. ### alchemy_cloudinary Render AlchemyCMS images directly from [Cloudinary](https://cloudinary.com) with smart resizing, cropping, and CDN delivery. ## Internationalization ### alchemy_i18n Translation files for AlchemyCMS. Provides admin interface translations for many languages. ## Error Tracking ### alchemy-sentry [Sentry](https://sentry.io) error reporting for AlchemyCMS. ### alchemy-bugsnag [Bugsnag](https://www.bugsnag.com) error reporting for AlchemyCMS. ### alchemy-appsignal [AppSignal](https://www.appsignal.com) error reporting for AlchemyCMS. ### alchemy-solid_errors Integrates [Solid Errors](https://github.com/yagudaev/solid_errors) into AlchemyCMS, adding a dashboard for error tracking. ## Background Jobs ### alchemy-mission_control-jobs Integrates [Mission Control Jobs](https://github.com/rails/mission_control-jobs) into AlchemyCMS, adding a dashboard for Active Job. --- ::: tip Created an Alchemy extension? If you have built an extension for AlchemyCMS and would like it listed here, please [open a pull request](https://github.com/AlchemyCMS/alchemy-guides/edit/main/guides/extensions.md) to add it. ::: --- URL: "/getting_started.html" LLMS_URL: "/getting_started.md" prev: false next: text: "Elements" link: "elements" --- # Getting Started This guide walks you through installing AlchemyCMS into a Rails application. By the end you will have a running Alchemy instance with an admin user. ## Prerequisites You need a working Ruby on Rails development environment. If you are new to Rails, follow the [Install Ruby on Rails](https://guides.rubyonrails.org/install_ruby_on_rails.html) guide first. - [Ruby](https://www.ruby-lang.org/en/documentation/installation/) >= 3.1 - [Ruby on Rails](https://rubyonrails.org) >= 7.2 - [ImageMagick](https://imagemagick.org) or [libvips](https://www.libvips.org) for image processing - A database: [PostgreSQL](https://www.postgresql.org), [MariaDB](https://www.mariadb.org), or [SQLite](https://sqlite.org) ::: tip Use a package manager to install system dependencies. [Homebrew](https://brew.sh) on macOS, or your Linux distribution's package manager. ::: ## Create a Rails Application If you don't have an existing Rails app, create one. ~~~ bash gem install rails rails new myapp cd myapp ~~~ ::: tip See the [Rails Getting Started guide](https://guides.rubyonrails.org/getting_started.html) for more options. ::: ## Install Alchemy Add the gem and run the installer. It will mount Alchemy in your routes, create configuration files, set up the database, and generate demo content. ~~~ bash bundle add alchemy_cms bin/rails alchemy:install ~~~ The installer asks for your site's primary language. Accept the defaults or enter your own. ## Authentication Alchemy lets you choose your own authentication strategy. **Option A: Use alchemy-devise** (recommended for new projects) Add the gem and run its installer. ::: tip See [Extensions](extensions) for all available add-ons. ::: ~~~ bash bundle add alchemy-devise bin/rails g alchemy:devise:install ~~~ **Option B: Use your existing authentication** If your app already has a user model, configure Alchemy to use it. See the [custom authentication guide](how_to_add_custom_authentication) for details. ## Start the Server ~~~ bash bin/rails server ~~~ Open [http://localhost:3000/admin](http://localhost:3000/admin) in your browser. You will be prompted to create the first admin user. After signing in you can start building pages. Continue with the [Elements](elements) guide to learn how to define your content structure. ## Changing the Mount Point The installer automatically mounts Alchemy at the root of your application. If your app has its own routes and you want Alchemy to handle only a subpath, change the mount point in `config/routes.rb`. ~~~ ruby # config/routes.rb Rails.application.routes.draw do # your app's routes resources :products # mount Alchemy under /cms instead of / mount Alchemy::Engine => "/cms" end ~~~ ::: warning Alchemy has a catch-all route for page URLs. Mount it after your own routes so they take priority. ::: ## What the Installer Creates The installer generates these files in your app. | File | Purpose | |------|---------| | `config/alchemy/elements.yml` | [Element](elements) definitions | | `config/alchemy/page_layouts.yml` | [Page layout](pages) definitions | | `config/alchemy/menus.yml` | Menu definitions | | `config/initializers/alchemy.rb` | Alchemy [configuration](configuration) | | `app/views/layouts/application.html.erb` | Application layout with Alchemy helpers | | `app/views/alchemy/page_layouts/_standard.html.erb` | Demo [page layout](pages#page-layouts) partial | | `app/views/alchemy/elements/_article.html.erb` | Demo [element](elements#rendering) partial | | `app/assets/stylesheets/alchemy/admin/custom.css` | Custom [admin styles](configuration#admin-javascript-and-css) | --- URL: "/how_to_add_custom_authentication.html" LLMS_URL: "/how_to_add_custom_authentication.md" --- # Custom Authentication Alchemy does not ship with its own authentication. The [alchemy-devise](extensions#alchemy-devise) gem provides a ready-made solution using Devise, but you can use any authentication system. You only need to tell Alchemy about your user class and implement a few methods. This guide covers configuration, the required `alchemy_roles` method, and recommended methods for a full integration. ## Configuration Tell Alchemy about your user class and authentication paths in an initializer. ### Available Options | Option | Default | Description | |--------|---------|-------------| | `user_class` | `"User"` | Your user model class name | | `login_path` | `"/login"` | Path to the login form | | `logout_path` | `"/logout"` | Path to the logout action | | `logout_method` | `"delete"` | HTTP verb for the logout action | | `signup_path` | `"/signup"` | Path to the signup form | | `current_user_method` | `:current_user` | Controller method that returns the current user | | `user_class_primary_key` | `:id` | Primary key column of your user model (useful for UUIDs) | ### Alchemy 8.1+ ~~~ ruby # config/initializers/alchemy.rb Alchemy.configure do |config| config.user_class = "Admin" config.login_path = "/auth/login" config.logout_path = "/auth/logout" config.logout_method = "delete" end ~~~ ### Alchemy 8.0 ~~~ ruby # config/initializers/alchemy.rb Alchemy.user_class_name = "Admin" Alchemy.login_path = "/auth/login" Alchemy.logout_path = "/auth/logout" ~~~ :::warning This syntax is deprecated since Alchemy 8.1 and will be removed in a future version. ::: ## Required The only method Alchemy needs from your user class is `alchemy_roles`. It must return an array of strings containing at least one of these roles: * `member` * `author` * `editor` * `admin` ~~~ ruby # app/models/user.rb def alchemy_roles if admin? %w(admin) else %w(member) end end ~~~ :::tip You can use your own authorization system to determine the role. Alchemy only needs an array with at least one of the roles listed above. ::: ## Recommended These methods and associations are not strictly required, but without them parts of the admin interface will show missing or placeholder information. ### `name` Used on the dashboard to greet the user ("Welcome back, [name]") and in the online users list. Without it, the greeting will be blank. ~~~ ruby def name read_attribute(:email) end ~~~ ### `alchemy_display_name` Shown in the "logged in as" area at the top right of the admin interface, and used for creator/updater/locker names on pages. Without it, these will show "unknown". ~~~ ruby def alchemy_display_name "#{firstname} #{lastname}".strip end ~~~ ### `language` A locale string for the user's preferred translation of the Alchemy admin interface. The user can always switch the language manually, but this sets the default. ~~~ ruby def language "nl" end ~~~ :::tip In a real application you would store this as a column in the users table. ::: ### `admins` Class method that returns a collection of admin users. Used to check if any admins exist (e.g. for signup eligibility). ~~~ ruby def self.admins where(admin: true) end ~~~ ### Page Tree Folding Alchemy remembers which pages a user has folded or expanded in the admin page tree. To enable this, add the following association: ~~~ ruby has_many :folded_pages, class_name: "Alchemy::FoldedPage" ~~~ Without this, the page tree will work but folding state won't be persisted per user. ### Change Tracking Alchemy stores `creator_id` and `updater_id` on records like pages, elements, and pictures. To track which user created or updated these records, add these columns to your users table: ~~~ bash bin/rails g migration AddCreatorIdAndUpdaterIdToUsers creator_id:integer:index updater_id:integer:index bin/rails db:migrate ~~~ ## Optional ### Tagging If you want your users to be taggable, include the `Alchemy::Taggable` module: ~~~ ruby class User < ApplicationRecord include Alchemy::Taggable end ~~~ This adds [Gutentag](https://github.com/pat/gutentag)-based tagging with the following methods: * `tag_names` / `tag_list` -- get or set tags * `tag_list=` -- set tags from a comma-separated string or array * `User.tagged_with(["tag1", "tag2"])` -- find tagged records * `User.tag_counts` -- list all unique tags ## Complete Example ~~~ ruby class User < ApplicationRecord include Alchemy::Taggable has_many :folded_pages, class_name: "Alchemy::FoldedPage" def alchemy_roles if admin? %w(admin) else %w(member) end end def name email end alias_method :alchemy_display_name, :name def self.admins where(admin: true) end end ~~~ --- URL: "/how_to_create_a_blog.html" LLMS_URL: "/how_to_create_a_blog.md" --- # How To: Create a Blog This guide walks you through building a blog with Alchemy. Each blog post is a [page](pages) with its own URL, giving you a built-in publishing workflow, publication dates, and tagging support. The blog overview page automatically lists all published posts. You will create two [page layouts](pages#defining-page-layouts) — one for the overview and one for individual posts — along with their elements and view partials. ## Define Elements Define your blog elements in `config/alchemy/elements.yml`. ### Post Header The post header captures all metadata in a single element — headline, author, hero image, and a teaser excerpt: ~~~ yaml # config/alchemy/elements.yml - name: post_header unique: true taggable: true ingredients: - role: headline type: Headline settings: levels: [1] - role: author type: Text - role: image type: Picture settings: size: 1200x600 crop: true - role: teaser type: Richtext ~~~ ::: tip The post's title, URL, and publication date come from the page. The `teaser` is used as a short excerpt on the overview page. With `taggable: true`, editors can [tag](elements#tagging) posts with categories. ::: ### Post Text A repeatable text section with a headline. The `anchor: from_value` setting auto-generates an `id` attribute from the headline text, enabling deep links to sections within a post: ~~~ yaml - name: post_text ingredients: - role: headline type: Headline settings: levels: [2, 3] anchor: from_value - role: text type: Richtext ~~~ ### Post Picture A repeatable image element with responsive `srcset` support: ~~~ yaml - name: post_picture ingredients: - role: image type: Picture settings: srcset: ["400x", "800x", "1200x"] sizes: ["(max-width: 600px) 400px", "(max-width: 900px) 800px", "1200px"] ~~~ ### Blog Overview The overview element with an editable headline. Its partial queries and renders the blog posts automatically: ~~~ yaml - name: blog_overview unique: true ingredients: - role: headline type: Headline settings: levels: [1] default: Blog ~~~ ## Define Page Layouts ~~~ yaml # config/alchemy/page_layouts.yml - name: blog unique: true cache: false # overview lists child pages, must reflect publish changes elements: - blog_overview autogenerate: - blog_overview - name: blogpost elements: - post_header - post_text - post_picture autogenerate: - post_header ~~~ ::: tip Editors create new blog posts by adding child pages under the blog overview page and selecting the `blogpost` layout. ::: ## Customize Views Generate the view partials for your elements and page layouts: ~~~ bash bin/rails g alchemy:elements --skip bin/rails g alchemy:page_layouts --skip ~~~ The generated page layout partials render all elements with `<%= render_elements %>`. For the blog post, wrap it in an `
` tag in `app/views/alchemy/page_layouts/_blogpost.html.erb`: ~~~ erb
<%= render_elements %>
~~~ Then customize the element partials in `app/views/alchemy/elements/`: ### `_post_header.html.erb` ~~~ erb <%= element_view_for(post_header, tag: "header") do |el| %> <%= el.render :headline %>

By <%= el.render :author %> ·

<%= el.render :teaser %> <%= el.render :image %> <% end %> ~~~ ### `_post_text.html.erb` ~~~ erb <%= element_view_for(post_text) do |el| %> <%= el.render :headline %> <%= el.render :text %> <% end %> ~~~ ### `_post_picture.html.erb` ~~~ erb <%= element_view_for(post_picture) do |el| %> <%= el.render :image %> <% end %> ~~~ ### `_blog_overview.html.erb` The overview element lists all published child pages: ~~~ erb <%= element_view_for(blog_overview) do |el| %> <%= el.render :headline %> <% posts = blog_overview.page.children.published.order(public_on: :desc) %> <%= render partial: "post_preview", collection: posts, as: :post %> <% end %> ~~~ ### `_post_preview.html.erb` ~~~ erb <% cache(post) do %> <% header = post.elements.find_by(name: "post_header") %> <% return unless header %>
<% if header.ingredient_by_role(:image)&.picture %> <%= render header.ingredient_by_role(:image), size: "600x300", crop: true %> <% end %>

<%= link_to post.title, show_alchemy_page_path(post) %>

<%= header.ingredient_by_role(:teaser)&.value&.truncate(200) %>

<% end %> ~~~ --- URL: "/how_to_create_a_contact_form.html" LLMS_URL: "/how_to_create_a_contact_form.md" --- # How To: Create a Contact Form Alchemy has a built-in contact form mailer that handles form submissions, validations, and email delivery. This guide walks you through setting it up. ::: info NOTE Alchemy relies on Rails' [ActionMailer](https://guides.rubyonrails.org/action_mailer_basics.html) to send emails. You need to configure ActionMailer to use an SMTP service (Gmail, Sendgrid, Mailgun, Amazon SES, etc.) in your Rails environment configuration. ::: ## Mailer Configuration Configure the mailer in your Alchemy initializer: ~~~ ruby # config/initializers/alchemy.rb Alchemy.config.tap do |config| config.mailer.mail_from = "noreply@example.com" config.mailer.mail_to = "contact@example.com" config.mailer.subject = "New contact form message" config.mailer.fields = %w[firstname lastname email message] config.mailer.validate_fields = %w[lastname email] end ~~~ ### Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `page_layout_name` | `String` | `"contact"` | Page layout name used for rendering the contact form | | `forward_to_page` | `Boolean` | `false` | Redirect to the success page after sending | | `mail_success_page` | `String` | `"thanks"` | URL name of the page shown after successful submission | | `mail_from` | `String` | `"your.mail@your-domain.com"` | Sender email address | | `mail_to` | `String` | `"your.mail@your-domain.com"` | Recipient email address | | `subject` | `String` | `"A new contact form message"` | Email subject line | | `fields` | `Array` | `[salutation, firstname, lastname, address, zip, city, phone, email, message]` | Field names for the contact form. These become attributes on the `Alchemy::Message` model | | `validate_fields` | `Array` | `[lastname, email]` | Fields validated for presence. Fields matching `email` are also validated for email format | ::: tip These defaults serve as fallbacks. You can make all of these configurable by editors through element ingredients (see below). ::: ## Creating the Contact Form Element Define a contact form element in `config/alchemy/elements.yml`: ~~~ yaml # config/alchemy/elements.yml - name: contactform ingredients: - role: mail_from type: Text validate: - presence - format: email - role: mail_to type: Text validate: - presence - format: email - role: subject type: Text validate: - presence - role: success_page type: Page validate: - presence ~~~ ::: info NOTE The ingredients `mail_to`, `mail_from`, `subject` and `success_page` are recognized by the `Alchemy::MessagesController`. When present on the element, they override the mailer configuration defaults. This gives editors full control over these values. ::: ## Creating the Page Layout Create a page layout for your contact page in `config/alchemy/page_layouts.yml`: ~~~ yaml # config/alchemy/page_layouts.yml - name: contact unique: true cache: false elements: - heading - contactform autogenerate: - contactform ~~~ ::: warning Disabling page caching with `cache: false` is important for contact form pages, otherwise the form's CSRF token may become stale. ::: ## The Contact Form View Generate the element view partial: ~~~ bash bin/rails g alchemy:elements --skip ~~~ Open `app/views/alchemy/elements/_contactform.html.erb` and replace the content with: ~~~ erb <%= element_view_for(element) do |el| %> <%= simple_form_for(@message ||= Alchemy::Message.new) do |form| %> <%= form.input :firstname %> <%= form.input :lastname %> <%= form.input :email %> <%= form.input :message, as: :text %> <%= form.hidden_field :contact_form_id, value: element.id %> <%= form.button :submit %> <% end %> <% end %> ~~~ ::: warning The `contact_form_id` hidden field is required. The `MessagesController` uses it to find the element and read its ingredient values for `mail_to`, `mail_from`, `subject`, and `success_page`. ::: If you use different or additional fields (like `company` or `phone`), make sure they are listed in `Alchemy.config.mailer.fields`. ::: tip Alchemy bundles [simple_form](https://github.com/heartcombo/simple_form), so you can use all Simple Form input types and options in your contact form view. ::: ## Translating Validation Messages Validation messages are passed through `I18n.t`, so you can translate them in your locale files: ~~~ yaml # config/locales/de.yml de: activemodel: attributes: alchemy/message: firstname: Vorname lastname: Nachname email: E-Mail message: Nachricht ~~~ ## Customizing the Mailer Template Alchemy ships with plain-text email templates for English, German, and Spanish. To customize the email content, create your own template: ~~~ erb <%# app/views/alchemy/messages_mailer/contact_form_mail.text.erb %> New contact form message: Name: <%= @message.firstname %> <%= @message.lastname %> Email: <%= @message.email %> Message: <%= @message.message %> ~~~ The template has access to `@message` with all configured field values as attributes. --- URL: "/how_to_create_custom_ingredients.html" LLMS_URL: "/how_to_create_custom_ingredients.md" prev: text: "Ingredients" link: "ingredients" --- # Custom Ingredients Alchemy provides many [built-in ingredient types](ingredients), but you can create your own to store custom data or associate ingredients with your existing models. Each custom ingredient consists of a model class, a view component for the frontend, and an editor component for the admin interface. Alchemy uses [ViewComponent](https://viewcomponent.org) for both. ::: tip The [alchemy-solidus](extensions#alchemy-solidus) extension is a good real-world example. It provides `SpreeProduct`, `SpreeTaxon` and `SpreeVariant` ingredient types that associate Alchemy elements with Solidus e-commerce models. ::: ## Using the Generator The ingredient generator creates the model and both components for you. ~~~ bash bin/rails g alchemy:ingredient MyIngredient ~~~ This creates three files: - `app/models/alchemy/ingredients/my_ingredient.rb` - the model - `app/components/alchemy/ingredients/my_ingredient_view.rb` - the frontend view - `app/components/alchemy/ingredients/my_ingredient_editor.rb` - the admin editor ::: tip Ingredients live under the `Alchemy::Ingredients` namespace. The type name in your `elements.yml` is the PascalCase class name, e.g. `MyIngredient`. ::: ## The Model The generated model extends `Alchemy::Ingredient`. The main content is stored in the `value` column (a `text` column). ~~~ ruby # app/models/alchemy/ingredients/my_ingredient.rb module Alchemy module Ingredients class MyIngredient < Alchemy::Ingredient # Set additional attributes that get stored in the `data` JSON column # store_accessor :data, :some, :attribute # Set a related_object alias for convenience # related_object_alias :some_association_name, class_name: "Some::Klass" end end end ~~~ If you need to store additional values beyond `value`, add them as attributes to the `data` column using [`store_accessor`](https://api.rubyonrails.org/classes/ActiveRecord/Store.html). Rails will create accessor methods for you. ### Type Casting the Value Since `value` is stored as text, you may want to cast it to a different type. Override the `value` method and use ActiveRecord type casting. ~~~ ruby # Cast to boolean (like the built-in Boolean ingredient) def value ActiveRecord::Type::Boolean.new.cast(self[:value]) end # Cast to datetime (like the built-in Datetime ingredient) def value ActiveRecord::Type::DateTime.new.cast(self[:value]) end # Cast to integer def value ActiveRecord::Type::Integer.new.cast(self[:value]) end ~~~ ### Allowing Settings Use `allow_settings` to define which settings from `elements.yml` are available to your ingredient. Only safelisted settings are accessible via the `settings` hash. ~~~ ruby class MyIngredient < Alchemy::Ingredient allow_settings %i[format display_mode] end ~~~ ~~~ yaml # config/alchemy/elements.yml - name: my_element ingredients: - role: custom type: MyIngredient settings: format: short display_mode: inline ~~~ ### Customizing the Preview Text The `preview_text` method controls what is shown in the element's title bar in the admin. By default it shows the first 30 characters of `value`. Override it to show something more meaningful. ~~~ ruby class Person < Alchemy::Ingredient related_object_alias :person, class_name: "My::Person" def preview_text(maxlength = 30) return unless person person.name[0..maxlength - 1] end end ~~~ ## The View Component The view component controls how the ingredient is rendered on your website. It extends `Alchemy::Ingredients::BaseView`. ~~~ ruby # app/components/alchemy/ingredients/my_ingredient_view.rb module Alchemy module Ingredients class MyIngredientView < BaseView def call content_tag(:div, ingredient.value) end end end end ~~~ The `ingredient` accessor gives you access to the ingredient record, including its `value`, `settings`, and any `data` attributes or associations. Override `call` to return the HTML you want to render. Override `render?` to control when the component renders. By default it renders when the ingredient has a value. ~~~ ruby class PersonView < BaseView delegate :person, to: :ingredient def call content_tag(:span, person.name) end def render? !!person end end ~~~ ## The Editor Component The editor component controls the form fields in the admin interface. It extends `Alchemy::Ingredients::BaseEditor`. ~~~ ruby # app/components/alchemy/ingredients/my_ingredient_editor.rb module Alchemy module Ingredients class MyIngredientEditor < BaseEditor # The default editor renders a text field for the `value` column. # Override `input_field` to customize the form input. # # def input_field # text_field_tag(form_field_name, value) # end end end end ~~~ The base editor provides helpers for building form fields: - `form_field_name(column)` - generates the correct form field name for a column (defaults to `"value"`) - `form_field_id(column)` - generates the correct form field ID for a column - `value` - the current ingredient value - `settings` - the ingredient's settings from the element definition Override the `input_field` method to customize the editor UI. For example, to render a select box: ~~~ ruby class MyIngredientEditor < BaseEditor def input_field select_tag( form_field_name, options_for_select(available_options, value), class: "full_width" ) end private def available_options settings[:options] || [] end end ~~~ ::: warning Avoid loading large collections into the editor. If your `input_field` renders a `select_tag` with all records from a model (e.g. all products or all people), every element editor will trigger a database query for the full set. This causes N+1 queries and slow admin pages. Instead, use a remote select that fetches results on demand via an API endpoint. Alchemy ships a `RemoteSelect` JavaScript base class (`alchemy_admin/components/remote_select`) that provides paginated, searchable selects using [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements). Your custom element extends `RemoteSelect`, implements `_searchQuery()` and `_parseResponse()`, and your editor component renders the custom element tag instead of a `select_tag`. The [alchemy-solidus](extensions#alchemy-solidus) extension demonstrates this pattern. It defines ``, `` and `` custom elements that query the Solidus API with pagination and search, avoiding loading thousands of records into memory. ::: ## Associating with Models You can associate any ActiveRecord model with an ingredient using `related_object_alias`. This stores the foreign key in the `related_object_id` column and sets up a `belongs_to` association. ~~~ ruby # app/models/alchemy/ingredients/person.rb module Alchemy module Ingredients class Person < Alchemy::Ingredient related_object_alias :person, class_name: "My::Person" def preview_text(maxlength = 30) return unless person person.name[0..maxlength - 1] end end end end ~~~ Alchemy handles the rest. You can access the associated model in your view component. ~~~ ruby # app/components/alchemy/ingredients/person_view.rb module Alchemy module Ingredients class PersonView < BaseView delegate :person, to: :ingredient def call content_tag(:span, person.name) end def render? !!person end end end end ~~~ ## Using Your Ingredient Add your custom ingredient to any element definition in `elements.yml`. ~~~ yaml - name: team_member ingredients: - role: person type: Person ~~~ See the [Elements guide](elements) for more on defining elements. --- URL: "/how_to_create_modules.html" LLMS_URL: "/how_to_create_modules.md" --- # Creating Modules Modules add custom sections to the Alchemy admin interface. Use them to manage your own models (events, products, orders) with Alchemy's built-in CRUD interface, authentication, and authorization. You only need three things: a controller, a menu entry, and routes. ## Generator Alchemy provides a generator that creates all the necessary files: ~~~ bash bin/rails generate alchemy:module event ~~~ This generates: | File | Purpose | |------|---------| | `app/controllers/admin/events_controller.rb` | Admin controller inheriting from `ResourcesController` | | `app/models/events_ability.rb` | CanCanCan ability for authorization | | `config/initializers/alchemy_event.rb` | Menu entry and ability registration | It also adds the admin routes to `config/routes.rb`. The rest of this guide explains what each piece does so you can customize or create modules manually. ## The Controller Your module controller inherits from `Alchemy::Admin::ResourcesController`, which provides a full CRUD interface out of the box: ~~~ ruby # app/controllers/admin/events_controller.rb class Admin::EventsController < Alchemy::Admin::ResourcesController end ~~~ That's it. The `ResourcesController` automatically generates index, new, create, edit, update, and destroy actions based on your model's attributes. It also handles search, pagination, sorting, and CSV export. You can override any action or add your own logic as needed. ## Routes Add admin routes for your resource. Make sure to define them **before** the Alchemy engine mount: ~~~ ruby # config/routes.rb Rails.application.routes.draw do namespace :admin do resources :events end mount Alchemy::Engine => "/" end ~~~ ::: warning Alchemy uses a catch-all route for pages. Always mount your routes before `Alchemy::Engine`, otherwise they won't be reachable. ::: ## Menu Entry Add your module to the admin navigation with `config.menu.add`: ~~~ ruby # config/initializers/alchemy_event.rb Alchemy.configure do |config| config.menu.add( name: "events", title: "Events", icon: "calendar", url: -> { main_app.admin_events_path }, condition: -> { can?(:index, :admin_events) } ) end ~~~ `url` and `condition` are evaluated in the view context, so they can use route helpers like `main_app` or your engine's routing proxy, and the `can?` permission check. ::: warning Add menu entries once, at boot. Unlike the old `register_module`, `config.menu.add` does not skip entries that are already present, so calling it inside a `to_prepare` block would add the entry again on every code reload in development. ::: ### Menu Entry Options | Option | Type | Description | |--------|------|-------------| | `name` | `String` | Unique identifier of the entry | | `title` | `String` | Display name or I18n key (e.g. `"modules.events"`) | | `url` | `Proc` | Returns the link target. Evaluated in the view context | | `condition` | `Proc` | Returns whether the entry is shown. Evaluated in the view context. Entries without a condition are shown to every admin user | | `position` | `Numeric` | Sort order in the navigation sidebar (lower numbers appear higher). The core entries use `1` to `7`, so `4.5` places an entry between Languages and Sites. Entries without a position are listed last | | `icon` | `String` | A [Remix icon](https://remixicon.com) name (e.g. `"calendar"`, `"shopping-bag"`) | | `icon_style` | `String` | Remix icon style: `"line"` (default), `"fill"`, or `"none"` | | `inline_image` | `String` | Raw HTML or inline SVG for a custom icon (used instead of `icon`) | | `image` | `String` | Path to an image asset for the icon (used instead of `icon`) | | `turbo` | `Boolean` | Set to `false` to load the entry's page without Turbo (Default: `true`) | | `match_path` | `Regexp` | Marks the entry as active when the request path matches it | | `children` | `Array` | Sub-navigation entries (see [Sub-Navigation](#sub-navigation)) | ::: info NOTE The icon options are checked in order: `image`, `inline_image`, `icon`. If none is set, a default table icon is rendered. ::: An entry is highlighted as active when the current page is its url or any page below it, so `/admin/events/1/edit` highlights an entry linking to `/admin/events`. Use `match_path` if your module's pages live elsewhere: ~~~ ruby config.menu.add( name: "events", title: "Events", url: -> { main_app.admin_events_path }, match_path: %r{\A/admin/(events|bookings)} ) ~~~ ### Sub-Navigation Add tabs within your module using `children`. Each child takes the same options as a menu entry: ~~~ ruby config.menu.add( name: "events", title: "Events", icon: "calendar", url: -> { main_app.admin_events_path }, condition: -> { can?(:index, :admin_events) }, children: [ { title: "Events", url: -> { main_app.admin_events_path }, condition: -> { can?(:index, :admin_events) } }, { title: "Locations", url: -> { main_app.admin_locations_path }, condition: -> { can?(:index, :admin_locations) } } ] ) ~~~ The main entry is also active while one of its children is. ::: info NOTE Each sub-navigation entry needs its own controller and routes. ::: ### Translating Titles Titles are translated through I18n under the `alchemy` namespace. Use a key like `modules.events` to make it translatable: ~~~ ruby config.menu.add( name: "events", title: "modules.events", ... ) ~~~ ~~~ yaml # config/locales/en.yml en: alchemy: modules: events: Events ~~~ ~~~ yaml # config/locales/de.yml de: alchemy: modules: events: Veranstaltungen ~~~ ::: tip See the [I18n guide](i18n) for all available translation scopes. ::: ## Authorization Alchemy uses [CanCanCan](https://github.com/CanCanCommunity/cancancan) for authorization. Create an ability class and register it so Alchemy knows about your module's permissions: ~~~ ruby # app/models/events_ability.rb class EventsAbility include CanCan::Ability def initialize(user) if user.present? && user.is_admin? can :manage, Event can :manage, :admin_events end end end ~~~ The symbol `:admin_events` is derived from the controller path -- `/admin/events` becomes `:admin_events`. Use it in the menu entry's `condition`, so the entry is only shown to users who may use the module. Register the ability in the same initializer as the menu entry: ~~~ ruby # config/initializers/alchemy_event.rb Alchemy.configure do |config| config.abilities.add("EventsAbility") config.menu.add(...) end ~~~ :::warning `Alchemy.register_ability` is deprecated since Alchemy 8.1 and will be removed in a future version. ::: ::: tip You can define more granular permissions by checking for specific roles instead of `is_admin?`. See the [CanCanCan documentation](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities) for details. ::: ## Resource Form The `ResourcesController` renders default views for all CRUD actions. You can override any view by placing a file with the same name in your app: ~~~ app/views/admin/events/ _form.html.erb # The form for new/edit _table.html.erb # The table on the index page index.html.erb # The full index page new.html.erb # Rendered in the Alchemy dialog edit.html.erb # Rendered in the Alchemy dialog ~~~ The default form auto-generates fields from your model's database columns. To customize it, create a `_form.html.erb` partial: ::: tip `alchemy_form_for` is a wrapper around [Simple Form](https://github.com/heartcombo/simple_form). You can use all Simple Form input types and options. ::: ~~~ erb <%= alchemy_form_for resource_instance_variable, url: resource_path(resource_instance_variable, search_filter_params) do |f| %> <%= f.input :name %> <%= f.input :starts_at, as: :date %> <%= f.input :description %> <%= f.submit Alchemy.t(:save) %> <% end %> ~~~ ## Resource Table Alchemy renders index tables using the `Alchemy::Admin::Resource::Table` ViewComponent. Without any customization, it auto-generates columns from your model's database columns (skipping `id`, `created_at`, and `creator_id` by default) and adds edit and delete buttons. Columns are sorted with `name` first, `updated_at` last, and booleans near the end. You can customize which attributes are skipped, restricted, or searchable by defining class methods on your model (see [Customizing attributes](#customizing-attributes)). To customize the table rendering, create a `_table.html.erb` partial and use the component's block API: ~~~ erb <%= render Alchemy::Admin::Resource::Table.new( resources_instance_variable, query: @query, search_filter_params: search_filter_params ) do |table| %> <% table.icon_column "calendar" %> <% table.column :name, sortable: true %> <% table.column :starts_at, sortable: true %> <% table.column :location do |event| %> <%= event.location&.name %> <% end %> <% table.edit_button %> <% table.delete_button %> <% end %> ~~~ ### Table Component Methods `column(name, sortable: false, header: nil, type: nil, &block)` Adds a table column. Without a block, the column renders the attribute value automatically. With a block, you control the cell content: ~~~ erb <% table.column :name, sortable: true %> <% table.column :status do |event| %> <%= event.status %> <% end %> ~~~ `icon_column(icon, style: nil, &block)` Adds an icon column. Pass a fixed icon name or use a block to determine the icon per row: ~~~ erb <% table.icon_column "calendar" %> <% table.icon_column do |event| %> <%= event.active? ? "check" : "close" %> <% end %> ~~~ `edit_button(tooltip: "Edit", dialog_title: nil, dialog_size: nil)` Adds an edit button that opens the edit view in the Alchemy dialog. `delete_button(tooltip: "Delete", confirm_message: "Are you sure?")` Adds a delete button with a confirmation prompt. ::: tip Alchemy uses its own admin views for pages, pictures, and attachments. You can browse them on [GitHub](https://github.com/AlchemyCMS/alchemy_cms/tree/main/app/views/alchemy/admin/resources) as a reference. ::: ## Customizing Attributes By default, Alchemy skips `id`, `created_at`, and `creator_id` from the table and form views. You can customize this behavior by defining class methods on your model. ### Skipping Attributes Hide attributes from both the table and the form: ~~~ ruby class Event < ApplicationRecord def self.skipped_alchemy_resource_attributes %w[id created_at updated_at secret_token] end end ~~~ ### Restricting Attributes Restricted attributes are shown in the table but cannot be edited in the form: ~~~ ruby class Event < ApplicationRecord def self.restricted_alchemy_resource_attributes %w[synced_at external_id] end end ~~~ ### Searchable Attributes By default, all `string` and `text` columns are searchable. To override: ~~~ ruby class Event < ApplicationRecord def self.searchable_alchemy_resource_attributes %w[name description location_name] end end ~~~ ### Belongs-to Relations Alchemy can display associated record names instead of foreign key IDs. Define `alchemy_resource_relations` to configure this: ~~~ ruby class Event < ApplicationRecord belongs_to :location def self.alchemy_resource_relations { location: { attr_method: "name", attr_type: "string" } } end end ~~~ This replaces the `location_id` column in the table and form with a select box showing each location's `name`. ## Modules in Engines If you are packaging your module as a Rails engine, link the menu entry through your engine's routing proxy and add it from an engine initializer: ~~~ ruby # lib/my_shop/engine.rb module MyShop class Engine < ::Rails::Engine isolate_namespace MyShop engine_name "my_shop" initializer "my_shop.admin_menu" do Alchemy.configure do |config| config.menu.add( name: "shop", title: "Shop", icon: "shopping-bag", url: -> { my_shop.admin_products_path }, condition: -> { can?(:index, :my_shop_admin_products) } ) end end end end ~~~ ::: info NOTE The routing proxy (`my_shop` above) is named after the `engine_name` set in your `Rails::Engine` class. Resource controllers in your engine use the engine's routes automatically. ::: Mount your engine **before** Alchemy in the host app's routes: ~~~ ruby # config/routes.rb Rails.application.routes.draw do mount MyShop::Engine => "/" mount Alchemy::Engine => "/" end ~~~ ::: tip The [alchemy-devise](extensions#alchemy-devise) and [alchemy-solidus](extensions#alchemy-solidus) extensions are good examples of engine-based modules. See [Extensions](extensions) for more. ::: ## Migrating from `register_module` Before Alchemy 8.4, modules were added to the navigation with `Alchemy::Modules.register_module`. It still works, but is deprecated and will be removed in Alchemy 9.0. The upgrade task rewrites `register_module` calls that use a plain hash into `config.menu.add` and lists everything it could not rewrite: ~~~ bash bin/rails alchemy:upgrade ~~~ To migrate a call by hand, move the keys of the `navigation` hash to the entry itself and replace `controller` and `action` with `url` and `condition`: | `register_module` | `config.menu.add` | |-------------------|-------------------| | `navigation: {name: ...}` | `title: ...` | | `navigation: {controller: ..., action: ...}` | `url: -> { ... }` and `condition: -> { can?(...) }` | | `navigation: {sub_navigation: [...]}` | `children: [...]` | | `navigation: {"icon-style": ...}` | `icon_style: ...` | | `navigation: {data: {turbo: false}}` | `turbo: false` | | `navigation: {params: {...}}` | No longer supported, add the parameters to the `url` instead | The partials `alchemy/admin/_main_navi`, `alchemy/admin/partials/_main_navigation_entry` and `alchemy/admin/partials/_sub_navigation` have been replaced by the `Alchemy::Admin::MainNavigation` components. If your app overrides one of them, move your changes into a menu entry or into the `:alchemy_main_navigation` content block, which is still rendered at the end of the navigation. --- URL: "/how_to_customize_tinymce.html" LLMS_URL: "/how_to_customize_tinymce.md" --- # Customizing the Richtext Editor Alchemy uses [TinyMCE](https://www.tiny.cloud) as the richtext editor for [Richtext ingredients](ingredients#richtext). You can customize the toolbar, add plugins, apply custom stylesheets, and configure each editor instance independently. ## Default Configuration Alchemy ships with a sensible default configuration: ~~~ ruby { skin: "alchemy", width: "auto", resize: true, min_height: 250, menubar: false, statusbar: true, toolbar: [ "bold italic underline | strikethrough subscript superscript | numlist bullist indent outdent | removeformat | fullscreen", "pastetext charmap hr | undo redo | alchemy_link unlink anchor | code" ], fix_list_elements: true, convert_urls: false, entity_encoding: "raw", paste_as_text: true } ~~~ ::: info NOTE Default plugins: `alchemy_link`, `anchor`, `charmap`, `code`, `directionality`, `fullscreen`, `link`, `lists`. ::: ## Global Customization To change TinyMCE settings for all richtext editors in your application, set `Alchemy::Tinymce.init` in an initializer. Your settings are **merged** with the defaults, so you only need to specify what you want to override. ~~~ ruby # config/initializers/alchemy.rb Alchemy::Tinymce.init = { toolbar: [ "bold italic underline | numlist bullist | removeformat | fullscreen", "pastetext | undo redo | alchemy_link unlink | code" ] } ~~~ ::: info NOTE Global settings apply to all richtext editors in all elements. Use [per-ingredient settings](#per-ingredient-customization) to customize individual editors. ::: ## Per-Ingredient Customization You can override TinyMCE settings for individual Richtext ingredients in `config/alchemy/elements.yml` using the `tinymce` setting: ~~~ yaml # config/alchemy/elements.yml - name: article ingredients: - role: text type: Richtext settings: tinymce: toolbar: - "bold italic underline | removeformat" - "pastetext | undo redo | alchemy_link unlink | code" ~~~ Per-ingredient settings are merged with the global configuration, so you only need to specify the options you want to change for that specific editor. ### Adding Style Formats Give editors a dropdown to apply predefined styles: ~~~ yaml - name: text type: Richtext settings: tinymce: toolbar: - "bold italic underline | styleselect | removeformat | fullscreen" - "pastetext | undo redo | alchemy_link unlink | code" style_formats: - title: "Subheadline" block: "h3" - title: "Quote" block: "blockquote" - title: "Small" inline: "small" ~~~ ::: tip Keep your style formats minimal. Alchemy's strength is structured content with separate elements for different content types. Resist the temptation to turn a single richtext field into a full page editor. ::: ## Toolbar Buttons The toolbar is defined as an array of strings. Each string is one toolbar row. Buttons are separated by spaces, groups are separated by `|`. Common buttons: | Button | Description | |--------|-------------| | `bold` `italic` `underline` `strikethrough` | Text formatting | | `subscript` `superscript` | Sub/superscript | | `numlist` `bullist` | Ordered and unordered lists | | `indent` `outdent` | Indentation | | `removeformat` | Clear formatting | | `pastetext` | Paste as plain text | | `alchemy_link` `unlink` | Alchemy's link dialog | | `anchor` | Insert anchor | | `charmap` | Special characters | | `code` | View/edit HTML source | | `fullscreen` | Toggle fullscreen editing | | `hr` | Horizontal rule | | `formatselect` | Block format dropdown | | `styleselect` | Style format dropdown | ::: tip See the [TinyMCE toolbar documentation](https://www.tiny.cloud/docs/tinymce/latest/available-toolbar-buttons/) for all available buttons. ::: ## Adding Plugins TinyMCE can be extended with [community and premium plugins](https://www.tiny.cloud/docs/tinymce/latest/plugins/). Download the plugin and place it in `vendor/assets/javascripts/tinymce/plugins/` inside your app. Then append it to `Alchemy::Tinymce.plugins` and add its buttons to the toolbar: ~~~ ruby # config/initializers/alchemy.rb Alchemy::Tinymce.plugins += ["table"] Alchemy::Tinymce.init = { toolbar: [ "bold italic underline | numlist bullist | removeformat | fullscreen", "pastetext | undo redo | alchemy_link unlink | table | code" ] } ~~~ ::: info NOTE Plugins added to `Alchemy::Tinymce.plugins` beyond the built-in set are automatically preloaded in the admin interface. ::: ## Custom Stylesheet You can apply a stylesheet to the TinyMCE editor area so the editing experience matches your site's typography: ~~~ ruby # config/initializers/alchemy.rb Alchemy::Tinymce.init = { content_css: "/assets/tinymce_content.css" } ~~~ Create the stylesheet at `app/assets/stylesheets/tinymce_content.css`. This file is loaded inside the editor's iframe and controls how text appears while editing. ::: info NOTE Alchemy automatically switches between a light (`alchemy`) and dark (`alchemy-dark`) editor skin based on the user's OS preference. Setting a custom `content_css` overrides the built-in dark mode content stylesheet, so make sure your stylesheet works in both modes if your editors use dark mode. ::: ## HTML Sanitization A Richtext ingredient is a trusted-content field: by default its value is rendered as-is, so whatever markup is saved ends up unescaped in the page. To restrict what gets rendered, configure a `sanitizer:` setting on the ingredient. When it is present, Alchemy sanitizes the value at render time with Rails' [SafeListSanitizer](https://github.com/rails/rails-html-sanitizer#safelistsanitizer), keeping only the tags and attributes you list: ~~~ yaml - name: text type: Richtext settings: sanitizer: tags: - p - strong - em - a - ul - ol - li - h2 - h3 attributes: - href - target - class ~~~ ::: warning Without a `sanitizer:` setting the value is rendered unescaped. Configure a `sanitizer:` on every Richtext ingredient and allow only the tags and attributes that element actually needs. ::: ## Configuration Syntax Any [TinyMCE configuration option](https://www.tiny.cloud/docs/tinymce/latest/editor-important-options/) can be used. When setting options in Ruby (initializer), convert JavaScript syntax to Ruby hashes: ~~~ js // JavaScript { block_formats: "Paragraph=p;Header 2=h2;Header 3=h3" } ~~~ ~~~ ruby # Ruby { block_formats: "Paragraph=p;Header 2=h2;Header 3=h3" } ~~~ When setting options in YAML (elements.yml), use standard YAML syntax: ~~~ yaml settings: tinymce: block_formats: "Paragraph=p;Header 2=h2;Header 3=h3" style_formats: - title: "Highlight" inline: "span" classes: "highlight" ~~~ ::: tip Use YAML anchors to share TinyMCE configurations across multiple elements: ~~~ yaml # config/alchemy/elements.yml - name: article ingredients: - role: text type: Richtext settings: tinymce: &minimal_tinymce toolbar: - "bold italic underline | alchemy_link unlink | code" - name: news ingredients: - role: body type: Richtext settings: tinymce: *minimal_tinymce ~~~ ::: --- URL: "/how_to_deploy_with_kamal.html" LLMS_URL: "/how_to_deploy_with_kamal.md" prev: false next: false --- # Deploy with Kamal [Kamal](https://kamal-deploy.org) deploys Docker containers to your own servers via SSH. It handles zero-downtime deploys, SSL certificates, and rolling restarts out of the box. This guide walks through deploying an Alchemy app with Kamal, based on a real production setup. ### Prerequisites - A server with SSH access (e.g. Hetzner, DigitalOcean, AWS EC2) - A container registry account (GitHub Container Registry, Docker Hub, etc.) - Docker running locally (unless building in CI/CD) ## Install Kamal Add Kamal to your Gemfile and run the initializer to generate the config files: ~~~bash bundle add kamal bin/kamal init ~~~ ## Dockerfile Kamal deploys Docker images, so you need a `Dockerfile`. Rails 7.1+ generates one for you. The key parts for Alchemy: ~~~dockerfile FROM docker.io/library/ruby:3.4.8-slim AS base WORKDIR /rails # Install runtime packages including an image processor RUN apt-get update -qq && \ apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # ... build stage installs gems and precompiles assets ... # Precompile assets without requiring credentials RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile # ... final stage copies built artifacts ... # Entrypoint prepares the database ENTRYPOINT ["/rails/bin/docker-entrypoint"] EXPOSE 80 CMD ["./bin/thrust", "./bin/rails", "server"] ~~~ :::tip Make sure `libvips` (or `imagemagick`) is in the base image's package list. Without it, image rendering will fail in production. ::: The entrypoint script should run database preparation on startup: ~~~bash #!/bin/bash -e if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then ./bin/rails db:prepare fi exec "${@}" ~~~ This runs `db:prepare` only when starting the web server, which handles both initial setup and migrations. ## Kamal Configuration Configure `config/deploy.yml`: ~~~yaml service: my-alchemy-app image: your-registry-user/my-alchemy-app servers: web: - 203.0.113.1 # your server IP proxy: ssl: true hosts: - example.com - www.example.com registry: server: ghcr.io username: your-github-user password: - KAMAL_REGISTRY_PASSWORD env: secret: - RAILS_MASTER_KEY clear: SOLID_QUEUE_IN_PUMA: true volumes: - "my_alchemy_app_storage:/rails/storage" asset_path: /rails/public/assets builder: arch: amd64 ~~~ Key settings: - **`proxy.ssl: true`** -- Kamal's proxy automatically obtains Let's Encrypt certificates - **`volumes`** -- Persists SQLite databases, Active Storage files, and cache across deploys - **`asset_path`** -- Bridges assets between old and new versions during deploy to avoid 404s on in-flight requests - **`builder.arch`** -- Set to `amd64` if your server runs x86_64 (common for most VPS providers) ## Secrets Edit `.kamal/secrets` to pull secrets from environment variables or a password manager: ~~~bash KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY=$RAILS_MASTER_KEY ~~~ :::warning Never commit `config/master.key` to git. Pass it through secrets instead. ::: Add any additional secrets your app needs to this file and reference them in the `env.secret` list in `config/deploy.yml`. ## Database Setup For a simple deployment, SQLite with a persistent Docker volume works well. The `volumes` entry in `config/deploy.yml` ensures the database survives deploys: ~~~yaml volumes: - "my_alchemy_app_storage:/rails/storage" ~~~ For PostgreSQL or MySQL, configure an accessory in `config/deploy.yml` or use an external managed database and set `DB_HOST` in your environment. The entrypoint's `db:prepare` handles both initial creation and subsequent migrations automatically. ## Storage For **local disk storage**, the Docker volume mount keeps files persistent. This is the simplest option for single-server deployments. For **remote storage** (S3, GCS, Azure), configure ActiveStorage as described in the [Deployment](deployment#remote-storage) guide. Remote storage is required if you run multiple servers or want files to survive a server replacement. ## First Deploy ~~~bash bin/kamal setup ~~~ This will: 1. Install Docker on your server (if needed) 2. Start the Kamal proxy 3. Build and push your Docker image 4. Start the container 5. Run the entrypoint (which calls `db:prepare`) Verify by visiting your domain. If you configured `proxy.ssl`, HTTPS should work automatically. ## Subsequent Deploys ~~~bash bin/kamal deploy ~~~ Kamal performs zero-downtime deploys by default: it starts the new container, waits for it to pass health checks, then stops the old one. ## Useful Commands ~~~bash # Open a Rails console on the server bin/kamal console # Tail production logs bin/kamal logs -f # Run a one-off command bin/kamal app exec "bin/rails alchemy:generate:thumbnails" # SSH into the running container bin/kamal app exec -i bash ~~~ You can define these as aliases in `config/deploy.yml`: ~~~yaml aliases: console: app exec --interactive --reuse "bin/rails console" logs: app logs -f ~~~ ## Destinations Kamal destinations let you deploy the same app to different environments. Create a destination-specific config file for each stage: ~~~yaml # config/deploy.staging.yml servers: web: - 203.0.113.2 # staging server proxy: ssl: true hosts: - staging.example.com ~~~ ~~~yaml # config/deploy.production.yml servers: web: - 203.0.113.1 # production server proxy: ssl: true hosts: - example.com - www.example.com ~~~ Deploy to a specific destination with the `-d` flag: ~~~bash bin/kamal setup -d staging bin/kamal deploy -d production ~~~ Each destination gets its own containers and proxy configuration on the target server. Secrets can also be scoped per destination using `.kamal/secrets.staging` and `.kamal/secrets.production`. ## CI/CD You can trigger Kamal deploys from GitHub Actions or any CI system. A minimal workflow: ~~~yaml # .github/workflows/deploy.yml name: Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - run: bin/kamal deploy env: KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} ~~~ :::tip The [Kamal documentation](https://kamal-deploy.org) covers additional topics like rolling deploys, accessories, and multi-server setups. ::: --- URL: "/how_to_extend_alchemy.html" LLMS_URL: "/how_to_extend_alchemy.md" --- # Extending Alchemy Alchemy has extensive [configuration](configuration) options and class-level extension points, but sometimes you need to go further. This guide is about using Alchemy's helpers in your own controllers, and adding behavior to Alchemy's built-in controllers and models. ::: tip Looking for ready-made add-ons? See the [Extensions](extensions) page. ::: ## Using Alchemy in Your Controllers If your app has controllers that render Alchemy content or use Alchemy view helpers like `render_elements` or `render_menu`, include `Alchemy::ControllerActions`: ~~~ ruby # app/controllers/blog_controller.rb class BlogController < ApplicationController include Alchemy::ControllerActions end ~~~ This gives your controller access to: - `current_alchemy_site` and `set_alchemy_language` - Alchemy's page and element view helpers - `current_alchemy_user` and `alchemy_user_signed_in?` ## Extending Alchemy Controllers To add behavior to an Alchemy controller, use Ruby's `prepend` inside a `config.to_prepare` block. This ensures the extension is reloaded in development. ~~~ ruby # config/initializers/alchemy_extensions.rb Rails.application.config.to_prepare do Alchemy::PagesController.prepend(PagesControllerExtension) end ~~~ ~~~ ruby # app/models/pages_controller_extension.rb module PagesControllerExtension def self.prepended(base) base.before_action :track_visit, only: :show end private def track_visit # your custom logic end end ~~~ ## Extending Alchemy Models The same pattern works for models: ~~~ ruby # config/initializers/alchemy_extensions.rb Rails.application.config.to_prepare do Alchemy::Page.prepend(PageExtension) end ~~~ ~~~ ruby # app/models/page_extension.rb module PageExtension def self.prepended(base) base.scope :featured, -> { where(page_layout: "featured") } base.after_publish :notify_subscribers end private def notify_subscribers # your custom logic end end ~~~ ::: tip `config.to_prepare` runs on every request in development and once in production, so your extensions are always in sync with code reloads. ::: --- URL: "/how_to_integrate_into_existing_rails_app.html" LLMS_URL: "/how_to_integrate_into_existing_rails_app.md" prev: false next: false --- # How To: Integrate Into an Existing Rails App This guide covers integrating AlchemyCMS into a Rails application that already has its own routes, controllers, and views. If you are starting a new project, see [Getting Started](getting_started) instead. ## Install Alchemy Add the gem and run the installer with `--skip-demo-files` to avoid overwriting your existing layout and views. ~~~ bash bundle add alchemy_cms bin/rails alchemy:install --skip-demo-files ~~~ The installer mounts Alchemy in your routes, creates [configuration files](getting_started#what-the-installer-creates), sets up the database, and seeds the default site and language. ## Authentication Alchemy needs to know which users can access the admin. Point it to your user model and authentication: ~~~ ruby # config/initializers/alchemy.rb Alchemy.configure do |config| config.user_class = "User" config.current_user_method = "current_user" config.login_path = "/login" config.logout_path = "/logout" end ~~~ Your user model must respond to `alchemy_roles` and return an array of role names: ~~~ ruby class User < ApplicationRecord def alchemy_roles admin? ? %w[admin] : %w[editor] end end ~~~ See the [custom authentication guide](how_to_add_custom_authentication) for the full list of required methods and configuration options. ## Routing The installer mounts Alchemy at the root path by default. Since Alchemy has a catch-all route for pages, it must be mounted **after** your own routes. ~~~ ruby # config/routes.rb Rails.application.routes.draw do resources :products resources :orders mount Alchemy::Engine => "/" end ~~~ ::: warning If Alchemy is mounted before your routes, its catch-all will intercept requests meant for your controllers. ::: ### Mounting at a Subpath If you prefer Alchemy to handle only a portion of your site, mount it at a subpath: ~~~ ruby mount Alchemy::Engine => "/cms" ~~~ The admin interface is then available at `/cms/admin` and Alchemy pages are served under `/cms/...`. ## Layout Alchemy renders pages through your `application.html.erb` layout. Add the Alchemy meta data partial and the edit mode bar to your existing layout: ~~~ erb <%= render "alchemy/pages/meta_data" if @page %> <%= yield %> <%= render "alchemy/edit_mode" if @page %> ~~~ The `if @page` guard ensures these partials only render on Alchemy-managed pages and don't interfere with your own controllers. ::: tip The meta data partial sets `` and `<meta name="description">` from the current Alchemy page. ::: ## Using Alchemy Helpers If your own controllers need access to Alchemy's view helpers (like `render_menu` for navigation or `current_alchemy_site`), include the `ControllerActions` module: ~~~ ruby class ApplicationController < ActionController::Base include Alchemy::ControllerActions end ~~~ This gives you: | Helper | Purpose | |--------|---------| | `render_menu` | Render an Alchemy navigation menu | | `render_breadcrumb` | Render a breadcrumb trail | | `current_alchemy_site` | The current site based on the request host | | `Current.language` | The current language | ::: tip If you only need Alchemy helpers in specific controllers, include the module there instead of in `ApplicationController`. ::: ### Rendering Alchemy Menus in Your Layout A common use case is rendering an Alchemy-managed navigation menu in your application layout, shared across both your own pages and Alchemy pages: ~~~ erb <!-- app/views/layouts/application.html.erb --> <nav> <%= render_menu do |node| %> <%= link_to node.name, node.url, class: node.active?(request) ? "active" : nil %> <% end %> </nav> ~~~ See the [Menus guide](menus) for more rendering options. ## Administrate App Resources Alchemy's admin interface is not limited to CMS content. You can add your own models (events, products, orders) as admin modules with full CRUD views, search, pagination, and authorization built in. ~~~ ruby # app/controllers/admin/events_controller.rb class Admin::EventsController < Alchemy::Admin::ResourcesController end ~~~ Register the module and it appears in the admin sidebar alongside pages, images, and other Alchemy modules. See the [Custom Modules guide](how_to_create_modules) for the full setup including routes, module registration, authorization, and view customization. ## Next Steps - [Elements](elements) — define your content structure - [Pages](pages) — configure page layouts - [Configuration](configuration) — customize Alchemy's behavior - [Custom Authentication](how_to_add_custom_authentication) — connect your existing user model - [Custom Modules](how_to_create_modules) — manage your own models in the Alchemy admin --- URL: "/i18n.html" LLMS_URL: "/i18n.md" prev: false next: false --- # Internationalization Alchemy uses [Rails I18n](https://guides.rubyonrails.org/i18n.html) for all admin interface labels. Element names, ingredient labels, page layout names, hints, and menu names are all translatable through standard locale files. ## How It Works Create a file named `config/locales/alchemy.en.yml` (or any locale) in your app. Translations are scoped under the `alchemy` key: ~~~ yaml # config/locales/alchemy.en.yml en: alchemy: page_layout_names: blog: Blog blogpost: Blog Post element_names: post_header: Post Header post_text: Text Block ingredient_roles: headline: Headline body: Body Text ~~~ Alchemy automatically picks up these files. Your translations override the gem's defaults. ::: tip If no translation is found, Alchemy humanizes the key. For example, `post_header` becomes "Post header". You only need translations if the humanized version is not good enough. ::: ## Translation Scopes ### Page Layout Names ~~~ yaml en: alchemy: page_layout_names: standard: Standard Page blog: Blog Overview contact: Contact ~~~ ### Element Names ~~~ yaml en: alchemy: element_names: article: Article post_header: Post Header contactform: Contact Form ~~~ ### Ingredient Roles Ingredient labels can be translated globally or per element. Element-specific translations take precedence: ~~~ yaml en: alchemy: ingredient_roles: # Global — used for all elements headline: Headline body: Body Text # Element-specific — overrides global for this element post_header: headline: Post Title ~~~ ### Menu Names ~~~ yaml en: alchemy: menu_names: main_menu: Main Navigation footer_menu: Footer Navigation ~~~ ### Deprecation Notices Elements and ingredients marked `deprecated: true` look up a translated notice. You can also pass a string directly. ~~~ yaml # config/alchemy/elements.yml - name: old_element deprecated: true ~~~ ~~~ yaml # config/locales/alchemy.en.yml en: alchemy: element_deprecation_notices: old_element: Please use the new_element instead ingredient_deprecation_notices: old_role: This ingredient will be removed ~~~ Ingredient deprecation notices can also be scoped per element under `ingredient_deprecation_notices.<element_name>.<role>`. ### Hints Hints are tooltips shown next to elements and ingredients in the admin. You can set them directly in the YAML definition or translate them via locale files. **Inline hint** in `elements.yml`: ~~~ yaml - name: post_header hint: The header shown at the top of each blog post ingredients: - role: headline type: Text hint: Displayed as the page title and in the browser tab ~~~ **Translated hint** — set `hint: true` in the definition, then add the translation: ~~~ yaml # config/alchemy/elements.yml - name: post_header hint: true ingredients: - role: headline type: Text hint: true ~~~ ~~~ yaml # config/locales/alchemy.en.yml en: alchemy: page_hints: blog: Overview page listing all blog posts element_hints: post_header: The header shown at the top of each blog post ingredient_hints: headline: Displayed as the page title and in the browser tab ~~~ ::: tip Use translated hints when you need to support multiple languages in the admin interface. Use inline hints for single-language setups. ::: ### Default Ingredient Texts Ingredients with a `default` value can use a symbol to look up translated default text: ~~~ yaml # config/alchemy/elements.yml - name: article ingredients: - role: body type: Richtext default: :lorem ~~~ ~~~ yaml # config/locales/alchemy.en.yml en: alchemy: default_ingredient_texts: lorem: Lorem ipsum dolor sit amet... ~~~ ### Ingredient Groups When ingredients are grouped in the element editor, the group headings are translatable. Like ingredient roles, they can be scoped per element: ~~~ yaml en: alchemy: element_groups: # Global metadata: Metadata # Element-specific article: metadata: Article Metadata ~~~ ### Picture CSS Classes CSS class labels for the Picture ingredient are translated under `picture_ingredients.css_classes`: ~~~ yaml en: alchemy: picture_ingredients: css_classes: left: Left aligned right: Right aligned no_float: No float ~~~ ## Admin Localization Alchemy ships with English translations only. The [alchemy_i18n](extensions#alchemy_i18n) gem provides community-maintained translations for many languages. Add it to your Gemfile: ::: tip See [Extensions](extensions) for all available add-ons. ::: ~~~ ruby gem "alchemy_i18n" ~~~ You can also add your own translations or override existing ones. Alchemy detects available admin locales from files matching the `alchemy.*.yml` pattern: ~~~ yaml # config/locales/alchemy.de.yml de: alchemy: page_layout_names: blog: Blog blogpost: Blogartikel element_names: post_header: Artikelkopf post_text: Textblock ingredient_roles: headline: Überschrift body: Fließtext ~~~ Alchemy picks the admin locale from the first available match in this order: 1. The locale select in the admin interface 2. The user's preferred language (if the user model responds to `language`) 3. The browser's `Accept-Language` header 4. The app's `I18n.default_locale` ## Using Alchemy.t In your own code (views, controllers, helpers), use `Alchemy.t` to look up translations under the `alchemy` scope: ~~~ ruby Alchemy.t(:save) # => I18n.t(:save, scope: [:alchemy]) Alchemy.t(:headline, scope: :element_names) # => I18n.t(:headline, scope: [:alchemy, :element_names]) ~~~ For translations outside the Alchemy namespace, use the standard `I18n.t` helper. ## Content Translation Content translation (translating page content for visitors) is handled through Alchemy's [multi-language system](languages), not through I18n. Each language gets its own page tree with separate content. See the [Languages guide](languages) for details. --- URL: "/ingredients.html" LLMS_URL: "/ingredients.md" prev: text: "Elements" link: "elements" next: text: "Pages" link: "pages" --- # Ingredients Ingredients are the individual content fields within an [element](elements). Each ingredient has a type that determines what kind of data it stores and how it is edited in the admin interface. Alchemy comes with a set of built-in ingredient types for common needs. You can also [create your own ingredient types](how_to_create_custom_ingredients). ::: tip The [alchemy-solidus](extensions#alchemy-solidus) extension is a good example of custom ingredients in action. It provides `SpreeProduct`, `SpreeTaxon` and `SpreeVariant` ingredient types that associate Alchemy elements with Solidus e-commerce models. ::: ## Global Settings Ingredients are defined within an [element definition](elements#defining-elements) in `config/alchemy/elements.yml`. Each ingredient requires a `role` and `type`. You can also set hints and default values. ~~~ yaml # config/alchemy/elements.yml - name: article ingredients: - role: headline type: Headline hint: This is the headline - role: color type: Text settings: input_type: color - role: copy type: Richtext default: Lorem ipsum dolor as_element_title: true ~~~ ### role `String` _required_ A lowercased **unique** (per element) identifier for the ingredient. Separate words with underscores. The role is used as the label in the admin and is [translatable](i18n#ingredient-roles). ### type `String` _required_ The ingredient type as a PascalCase class name (e.g. `Text`, `Richtext`, `Picture`). This maps to a class under `Alchemy::Ingredients`, so `Text` resolves to `Alchemy::Ingredients::Text`. Alchemy provides built-in types for [plain text](#text), [rich text](#richtext), [images](#picture), [booleans](#boolean) and more. ### hint `String|Symbol|Boolean` A hint for the editor in the admin interface that describes what the ingredient is used for. Set to `true` to use a [translated hint](i18n#hints) from your locale files. ### default `String` The default value to prefill newly created elements. You can also use a symbol to reference a [translated default text](i18n#default-ingredient-texts). ### as_element_title `Boolean` By default, the first ingredient is used for the element's title in the admin. Use this setting to override that and use a different ingredient instead. ### settings `Hash` Type-specific options to configure the ingredient. Each type has its own set of options listed below. ## Text Stores plain text of 255 characters max. Use this for headlines, product names, or short labels. The editor renders a single-line input field. The output is sanitized and HTML-escaped. ### Example ~~~ yaml - role: button type: Text settings: linkable: true ~~~ ### Settings #### linkable `Boolean` If set to `true`, the editor can add a link to the text. #### input_type `String` Change the [input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types) of the form field displayed to the editor. #### anchor `Boolean|String` Controls the DOM ID (`id` attribute) on the rendered element. Set to `true` to let the editor enter a custom anchor. Set to `from_value` to auto-generate it from the ingredient's value. Set to any other string to use that as a fixed anchor. ## Headline Stores headline text of 255 characters max. Use this for HTML headings. The editor renders a single-line input field with level and optional size selects. The view outputs a heading element (`<h1>` through `<h6>`). ### Example ~~~ yaml - role: headline type: Headline settings: levels: [2, 3] sizes: [1, 2] ~~~ ### Settings #### anchor `Boolean|String` Controls the DOM ID (`id` attribute) on the heading tag. Set to `true` to let the editor enter a custom anchor. Set to `from_value` to auto-generate it from the headline text. Set to any other string to use that as a fixed anchor. #### levels `Array` of `Integer`s Set the available [heading levels](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) the editor can choose from. Defaults to 1 through 6. #### sizes `Array` of `Integer`s Add a list of visual sizes the editor can choose from. The selected size is added as a CSS class (e.g. `.h2`) to the heading element, allowing you to decouple visual size from semantic level. ## Richtext Stores paragraphs of formatted text. The editor renders a textarea with an embedded TinyMCE editor. ### Example ~~~ yaml - role: text type: Richtext settings: tinymce: style_formats: - title: 'Subheadline' block: 'h3' ~~~ ::: tip See the [TinyMCE customization guide](how_to_customize_tinymce#per-ingredient-customization) for all available options. ::: ### Settings #### tinymce `Hash` Customize the TinyMCE editor for this ingredient. #### sanitizer `Hash` Restrict which HTML tags and attributes the rendered value may contain. When set, the value is sanitized at render time; without it the value is rendered as-is. Accepts `tags` and `attributes` arrays. See the [TinyMCE customization guide](how_to_customize_tinymce#html-sanitization) for details. ## Picture Stores a reference to a picture from the Alchemy library. The editor renders a picture selector with cropping options. The view renders the assigned picture, resized and cropped as configured. ::: tip See the [rendering images](render_images) guide for more on Alchemy's image rendering engine. ::: ### Example ~~~ yaml - role: hero_image type: Picture settings: size: 1200x600 crop: true srcset: ['400x200', '800x400', '1200x600'] sizes: ['(max-width: 600px) 400px', '(max-width: 900px) 800px', '1200px'] ~~~ ### Settings #### size `String` The size to downscale the image to, e.g. `400x300`. #### crop `Boolean` (Default: `false`) Crop the image to the given size. This also enables the built-in cropper tool. #### format `String` The format to render the image in: `jpg`, `png`, `gif` or `webp`. Only applied if the picture can be converted, so SVG files are rendered as they are. Defaults to the global `image_output_format` [configuration](render_images#configuration). ~~~ yaml - role: hero_image type: Picture settings: format: webp ~~~ #### srcset `Array<String, Hash>` A list of image sizes to use as sources. Best used with the `sizes` setting for responsive images. <Badge type="tip" text="8.4+" /> An entry can also be a hash with a `size` and its own `format`. Entries without a format use the `format` setting. See [responsive images](render_images#responsive-images). ~~~ yaml - role: hero_image type: Picture settings: srcset: - 400x200 - size: 800x400 format: webp ~~~ #### sizes `Array<String>` A list of media conditions for the image sources. Best used with the `srcset` setting for responsive images. #### css_classes `Array<String>` CSS classes the editor can choose from. The class labels are [translatable](i18n#picture-css-classes). ~~~ yaml - role: hero_image type: Picture settings: css_classes: ['left', 'right', 'no_float'] ~~~ #### fixed_ratio `Boolean` (Default: `false`) If set to `true`, the crop ratio is locked to the aspect ratio of the `size` setting. Requires `crop: true` and a `size` with both dimensions. #### linkable `Boolean` (Default: `true`) If set to `false`, the editor cannot add a link to the picture. #### upsample `Boolean` (Default: `false`) If set to `true`, images smaller than the requested `size` are upscaled. ## Select Stores a value selected from a list of options. The editor renders a select box. With `multiple: true`, multiple values can be selected and the value is stored as an array. ### Example ~~~ yaml - role: width type: Select default: '300' settings: select_values: ['200', '300', '400'] ~~~ ### Settings #### select_values `Array` The values the editor can choose from. Use [a two-dimensional array](https://guides.rubyonrails.org/form_helpers.html#the-select-and-option-tags) for separate display text and stored value pairs. #### display_inline `Boolean` If set to `true`, renders the select box inline in the element editor. #### multiple <Badge type="tip" text="8.1+" /> `Boolean` (Default: `false`) Allow the editor to select multiple values. When enabled, the value is stored as a JSON array internally. The `value` method returns an array instead of a string. ~~~ yaml - role: categories type: Select settings: select_values: - ['Ruby on Rails', 'rails'] - ['Single Page App', 'spa'] - ['Static Site', 'static'] multiple: true ~~~ Accessing the values in your template: ~~~ erb <%= element_view_for(element) do |el| %> <% el.value(:categories).each do |category| %> <span class="badge"><%= category %></span> <% end %> <% end %> ~~~ ::: tip If you need dynamic values (e.g. from a product catalogue), [create a custom ingredient type](how_to_create_custom_ingredients) instead. ::: ## Link Stores a URL. Useful for linking content where the editor should not set the link text directly. ### Settings #### text `String` Default link text. ::: tip If you want the link text to be editable, use [Text](#text) with `linkable: true` instead. ::: ## Number Stores a number value. Useful for prices, durations, quantities, etc. ### Settings #### input_type `String|Symbol` The input type for the editor. Defaults to `number`. Can be set to `range`. #### step `Integer|Decimal` The step increment for the input. #### min `Integer|Decimal` The minimum allowed value. #### max `Integer|Decimal` The maximum allowed value. #### unit `String` The unit label displayed next to the input. ## Color <Badge type="tip" text="8.1+" /> Stores a string value representing a color. Without any settings, the editor renders a free color picker. Values are not restricted to hex codes -- you can use any string: hex values, CSS color names, CSS custom properties, CSS class names, or any other token your frontend understands. ### Predefined Colors Use the `colors` setting to offer a palette of predefined options: ~~~ yaml - role: brand_color type: Color settings: colors: - "#ff0000" - "var(--primary)" - "bg-blue-500" ~~~ When using simple strings, the value is used as both the label and the swatch preview. ### Named Colors with Swatches Define colors as hashes to control the label, stored value, and swatch preview independently: ~~~ yaml - role: theme_color type: Color settings: colors: - name: Primary value: "var(--color-primary)" swatch: "#3b82f6" - name: Secondary value: "var(--color-secondary)" swatch: "#8b5cf6" - name: Danger value: danger swatch: "#ef4444" ~~~ The `swatch` controls the color shown in the select dropdown. This is useful when the stored value is not a visual color (like a CSS custom property or class name). If `swatch` is omitted, it defaults to the `value`. ### Custom Color When using predefined colors, you can allow a free color picker alongside the palette: ~~~ yaml - role: accent_color type: Color settings: colors: - name: Primary value: "var(--color-primary)" swatch: "#3b82f6" custom_color: true ~~~ This adds a "Custom color" option to the select that opens a free color picker for arbitrary values. ## File Stores a reference to a file attachment from the Alchemy library. The view renders a download link (`<a>` tag) to the attachment. ### Example ~~~ yaml - role: download type: File settings: only: ['pdf', 'zip'] ~~~ ### Settings #### link_text `String` Default text for the download link. Falls back to the attachment name. #### css_classes `Array<String>` CSS classes available for the editor to choose from. #### except `Array<String>` Exclude attachments with these file extensions from the selection. #### only `Array<String>` Only show attachments with these file extensions in the selection. ## Audio Stores a reference to an audio attachment from the Alchemy library. The view renders an HTML `<audio>` element with a `<source>` child. ### Example ~~~ yaml - role: podcast_player type: Audio settings: controls: true ~~~ ### Settings #### controls `Boolean` (Default: `false`) Show playback controls. #### autoplay `Boolean` (Default: `false`) Start playing automatically. #### muted `Boolean` (Default: `false`) Start muted. #### loop `Boolean` (Default: `false`) Loop playback. #### except `Array<String>` Exclude attachments with these file extensions from the selection. #### only `Array<String>` Only show attachments with these file extensions in the selection. ## Video Stores a reference to a video attachment from the Alchemy library. The view renders an HTML `<video>` element with a `<source>` child. ### Example ~~~ yaml - role: background_video type: Video settings: autoplay: true muted: true loop: true playsinline: true ~~~ ### Settings #### controls `Boolean` (Default: `false`) Show playback controls. #### autoplay `Boolean` (Default: `false`) Start playing automatically. #### muted `Boolean` (Default: `false`) Start muted. #### loop `Boolean` (Default: `false`) Loop playback. #### playsinline `Boolean` (Default: `false`) Play inline on mobile devices instead of fullscreen. #### preload `String` The preload strategy. Accepts `none`, `metadata`, or `auto`. #### width `String` The width of the video element. #### height `String` The height of the video element. #### except `Array<String>` Exclude attachments with these file extensions from the selection. #### only `Array<String>` Only show attachments with these file extensions in the selection. ## Datetime Stores a date and time value. The editor renders a datepicker. The view output is passed through Rails' I18n library, so it is fully localizable. ### Settings #### date_format `String|Symbol` Either a `String` with the format of [Ruby's `strftime`](https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/Date.html#method-i-strftime) or a `Symbol` of a [date/time format](https://guides.rubyonrails.org/i18n.html#adding-date-time-formats) defined in your locale files. ## Page References an [Alchemy page](pages). The editor renders a page select box. Useful for follow-up pages, related content links, or any case where you need to reference another page. ### Example ~~~ yaml - name: contact_form ingredients: - role: follow_up_page type: Page ~~~ ### Settings #### query_params `Hash` Additional query parameters appended to the page URL. ## Node References an Alchemy menu node. The editor renders a node select box. Useful for linking to specific menu entries or navigation items. ### Example ~~~ yaml - name: call_to_action ingredients: - role: menu_link type: Node ~~~ ### Settings #### query_params `Hash` Additional query parameters appended to the node URL. ## Boolean Stores a boolean value. The editor renders a checkbox. ## Html Stores raw HTML code, useful for embeds or tracking snippets. ::: warning The view renders the raw, unsanitized output. Be careful with user-provided content. ::: --- URL: "/languages.html" LLMS_URL: "/languages.md" prev: false next: false --- # Languages Alchemy has built-in multi-language support. Each [site](sites) can have multiple languages, each with its own page tree, menus, and content. ## How Languages Work Every language belongs to a site and is defined by a language code (e.g. `en`, `de`) and an optional country code (e.g. `US`, `AT`). One language per site must be marked as the default. | Attribute | Description | |-----------|-------------| | **Name** | Display name (e.g. "English", "Deutsch") | | **Language code** | ISO 639-1 code (e.g. `en`, `de`, `fr`) | | **Country code** | ISO 3166 code (e.g. `US`, `AT`, `CH`) | | **Public** | Whether the language is visible to visitors | | **Default** | The fallback language for the site (always public) | | **Frontpage name** | Name of the root page for this language | | **Page layout** | Default page layout for the root page | ## Creating a New Language Add languages through the admin interface under the Languages module. When you create a new language, Alchemy creates a root page for it using the configured page layout. ## Switching Languages in the Admin Editors switch between languages using the language selector in the admin interface. Each language has its own page tree and menu structure that editors manage independently. ## Single-Language Setups The installer creates a default language during setup. If your site only needs one language, no additional configuration is required. --- URL: "/library.html" LLMS_URL: "/library.md" prev: false next: false --- # Library The library is Alchemy's media management module. It stores **pictures** (images) and **attachments** (downloadable files) that editors use in page content. ## Pictures Pictures are images that editors assign to [Picture ingredients](ingredients#picture) on elements. Alchemy stores the original image and generates resized variants on the fly. See [Rendering Images](render_images) for details on how images are processed and displayed. The picture library supports: - Uploading single or multiple images at once - Tagging pictures for organization - Language-specific descriptions and captions - Filtering by file format, tags, or upload date - Built-in image cropping tool ::: tip Pictures that are assigned to elements cannot be deleted. Remove them from all elements first. ::: ## Attachments Attachments are downloadable files like PDFs, documents, or archives. Editors assign them to [File ingredients](ingredients#file) on elements. The attachment library supports: - Uploading single or multiple files at once - Tagging files for organization - Filtering by file type, tags, or upload date - Replacing a file while keeping the same record and URL ## Remote Storage In production you typically store media on a remote service like Amazon S3 instead of the local filesystem. See [Rendering Images: Remote storage](render_images#remote-storage) for setup instructions. ## Upload Settings Configure allowed file types and size limits in the [initializer](configuration#uploads). --- URL: "/menus.html" LLMS_URL: "/menus.md" prev: text: "Pages" link: "pages" next: text: "Rendering Images" link: "render_images" --- # Menus Alchemy uses a dedicated menu system that is separate from the page tree. Menus are managed through **nodes** — tree-structured entries that can link to Alchemy pages, external URLs, or act as grouping labels. This separation gives editors full control over navigation structure without being constrained by the page hierarchy. A page can appear in multiple menus, or not appear in any menu at all. ## Defining Menus Menu types are defined in the `config/alchemy/menus.yml` file. The installer creates this file with two default menus. ~~~ yaml # config/alchemy/menus.yml - main_menu - footer_menu ~~~ Each entry is a menu type name. Alchemy creates a root node for each menu type per language. Editors then build the menu tree by adding child nodes in the admin interface. ## Generating Menu Partials Alchemy provides a generator that creates view partials for all defined menus. ~~~ bash bin/rails g alchemy:menus ~~~ For each menu type, the generator creates two partials: | File | Purpose | |------|---------| | `app/views/alchemy/menus/<menu_type>/_wrapper.html.erb` | The outer container (e.g. `<ul>`) | | `app/views/alchemy/menus/<menu_type>/_node.html.erb` | A single menu item (e.g. `<li>`) | ## Rendering Menus Use the `render_menu` helper in your layout to render a menu. ~~~ erb <%# app/views/layouts/application.html.erb %> <nav> <%= render_menu "main_menu" %> </nav> <footer> <%= render_menu "footer_menu" %> </footer> ~~~ The helper finds the root node for the given menu type in the current language and renders the wrapper partial. You can pass custom options that are available in your partials. ~~~ erb <%= render_menu "main_menu", class: "navbar" %> ~~~ ## Customizing Menu Partials ### The Wrapper Partial The wrapper partial receives the root node as `menu` and any options you passed to `render_menu`. ~~~ erb <%# app/views/alchemy/menus/main_menu/_wrapper.html.erb %> <% cache [menu, @page, Alchemy::Current.preview_page?] do %> <ul class="nav"> <%= render partial: menu.to_partial_path, collection: menu.children.includes(:page, :children), locals: { options: options }, as: 'node' %> </ul> <% end %> ~~~ ### The Node Partial The node partial receives each node and renders it as a menu item. Child nodes are rendered recursively. ~~~ erb <%# app/views/alchemy/menus/main_menu/_node.html.erb %> <% cache [node, @page, Alchemy::Current.preview_page?] do %> <%= content_tag :li, class: ['nav-item', node.children.any? ? 'dropdown' : nil].compact do %> <%= link_to_if node.url, node.name, Alchemy::Current.preview_page? ? 'javascript: void(0)' : node.url, class: ['nav-link', current_page?(node.url) ? 'active' : nil].compact, title: node.title, target: node.external? ? '_blank' : nil, rel: node.nofollow? ? 'nofollow' : nil %> <% if node.children.any? %> <ul class="dropdown-menu"> <%= render node.children.includes(:page, :children), as: 'node' %> </ul> <% end %> <% end %> <% end %> ~~~ Customize these partials to match your site's markup and CSS framework. ## Node Properties Each node has the following properties that editors can set in the admin interface. ### Page An optional Alchemy page. When set, the node's name and URL are taken from the page automatically. ### Name A custom label. Required when no page is linked. When a page is linked, the page name is used unless a custom name is provided. ### Title An optional title attribute for the link tag. ### URL An optional external URL. When a page is linked, the page's URL is used instead. ### External Marks the link as external. When `true`, the link opens in a new tab (`target="_blank"`). ### Nofollow When `true`, adds `rel="nofollow"` to the link. ## Translating Menu Names Menu type names are translated through I18n. These names appear as root node labels in the admin interface. ~~~ yaml # config/locales/alchemy.de.yml de: alchemy: menu_names: main_menu: Hauptnavigation footer_menu: Fusszeile ~~~ ::: tip See the [I18n guide](i18n) for all available translation scopes. ::: --- URL: "/pages.html" LLMS_URL: "/pages.md" prev: text: "Ingredients" link: "ingredients" next: text: "Menus" link: "menus" --- # Pages Pages in Alchemy do not hold content directly. Content is stored in [ingredients](ingredients) inside [elements](elements) that are placed on pages. A page defines which elements are available, how caching behaves, and where the page appears in the site's URL structure. Pages are organized as a nested tree and have a unique URL slug. Each page has attributes for name, title, visibility, published status, and SEO metadata like meta description and meta keywords. Every page has a [page layout](#defining-page-layouts) — a definition that controls which elements can be placed on it and how the page behaves. ## Defining Page Layouts Page layouts are defined in the `config/alchemy/page_layouts.yml` file. Each page layout needs at least a name. ### Example A contact page with a headline, a contact form, and a text element. The page should be unique (only one per language), and must not be cached because of form validation messages. ~~~ yaml # config/alchemy/page_layouts.yml - name: contact cache: false unique: true elements: [headline, contactform, text] autogenerate: [headline, contactform] ~~~ ## Page Layout Settings ### name `String` _required_ A lowercased unique name for the page layout. Separate words with underscores. This name is used to match the view partial in `app/views/alchemy/page_layouts/` and is [translatable](#translating-page-layout-names) for the admin interface. ### elements `Array` A list of [element](elements) names that can be placed on pages of this type. Elements are defined in `config/alchemy/elements.yml`. ~~~ yaml - name: homepage elements: [hero, article, gallery] ~~~ ### autogenerate `Array` A list of element names that are created automatically when a new page of this type is created. ### unique `Boolean` (Default: `false`) When `true`, this page layout can only be used once per language tree. ### cache `Boolean` (Default: `true`) Set to `false` to disable cache headers for pages of this type. Recommended for pages with forms. ### layoutpage `Boolean` (Default: `false`) Marks this as a [global page](#global-pages) — a page outside the normal page tree used for shared elements like headers and footers. ### hide `Boolean` (Default: `false`) When `true`, this page layout is hidden from the "create new page" dialog. ### searchresults `Boolean` (Default: `false`) When `true`, this page layout is used to render fulltext search results. ### searchable `Boolean` (Default: `true`) When `false`, pages of this type are excluded from the built-in fulltext search. ### editable_by `String|Array` Restrict which [user roles](configuration#user-roles) can edit pages of this type. ### fixed_attributes `Hash` Lock specific page attributes to fixed values that editors cannot change. The fixed values are enforced at save time and override any editor changes. ~~~ yaml - name: readonly_page fixed_attributes: public_on: ~ public_until: ~ restricted: false ~~~ ### insert_elements_at `String` Set to `top` to insert new elements at the top of the page instead of the bottom (default). ### hint `String|Boolean` A tooltip displayed to content editors when selecting this page layout. Set to `true` to use a [translated hint](i18n#hints) from your locale files. ## Page Templates Each page layout has a view partial that is rendered inside the application layout (`app/views/layouts/application.html.erb`). Page layout partials live in `app/views/alchemy/page_layouts/` and are named after the page layout. ::: warning NOTE If no partial is found for a page layout, the `_standard.html.erb` partial is rendered instead. ::: ### Template Generator Alchemy provides a generator that creates page layout partials for all defined layouts. ~~~ bash bin/rails g alchemy:page_layouts --skip ~~~ ::: tip Pass `--template-engine` or `-e` to use `haml`, `slim`, or `erb`. The default follows your Rails application's template engine setting. ::: ### Rendering Elements The generated partial contains a single call to render all elements on the page. ~~~ erb <%= render_elements %> ~~~ Customize the HTML around this call to match your design. The `render_elements` helper accepts several options: ~~~ erb <%# Render only specific elements %> <%= render_elements only: "article" %> <%# Render all except certain elements %> <%= render_elements except: ["header", "footer"] %> ~~~ ## Global Pages Global pages (or layout pages) live outside the normal page tree. They are never rendered on their own. Use them to store shared elements that appear on multiple pages — for example, a footer, header, or tracking codes. To define a global page, set `layoutpage: true` in the page layout definition. ### Rendering Elements from a Global Page Use the `from_page` option of the `render_elements` helper. Pass an `Alchemy::Page` instance. ~~~ erb <%# app/views/layouts/application.html.erb %> <header> <%= render_elements only: "header", from_page: Alchemy::Page.find_by(page_layout: "header") %> </header> <main> <%= yield %> </main> <footer> <%= render_elements only: "footer", from_page: Alchemy::Page.find_by(page_layout: "footer") %> </footer> ~~~ ## Publishing Pages support time-based publishing with two timestamps: - **`public_on`** — the date and time the page becomes visible to visitors - **`public_until`** — the date and time the page is automatically unpublished Editors can set these in the page settings dialog. When a page is published, Alchemy duplicates the draft version's elements into the public version. Visitors always see the public version. ::: tip Setting `public_on` to a future date schedules the page for publication. Setting `public_until` lets you create time-limited content like promotions or announcements. ::: ## Caching Page caching is enabled by default. When active, page requests deliver `Cache-Control` headers that browsers, CDNs, and proxies use to cache the page. See the [Configuration guide](configuration#caching) for global cache settings. ::: tip Set `cache: false` on individual page layouts to disable caching for pages with forms or personalized content. ::: ### Fragment Caching Use Rails' [fragment caching](https://guides.rubyonrails.org/caching_with_rails.html#fragment-caching) to cache page templates. ~~~ erb <% cache @page do %> <%= render_elements %> <% end %> ~~~ ::: warning Do not cache page templates that contain forms (contact forms, comment forms, etc.). Rails' CSRF protection token is inside the `<form>` tag and caching it will break form submissions. ::: ## SEO Metadata Pages have `title`, `meta_description`, and `meta_keywords` attributes for SEO. Editors can set these in the page settings dialog. ::: tip NOTE Starting with Alchemy 8.1, these attributes have moved to `PageVersion`. This means SEO metadata changes need to be published just like content changes. In Alchemy 8.0, they are still stored directly on the page. ::: ## URL Redirects When a page's URL changes — because it was renamed or moved in the tree — Alchemy automatically creates a 301 redirect from the old URL to the new one. These redirects can be managed in the admin page settings. ## Translating Page Layout Names Page layout names are passed through Rails' I18n. ~~~ yaml # config/locales/alchemy.de.yml de: alchemy: page_layout_names: contact: Kontakt search: Suche ~~~ ::: tip See the [I18n guide](i18n) for all available translation scopes. ::: --- URL: "/render_images.html" LLMS_URL: "/render_images.md" prev: text: "Menus" link: "menus" next: text: "Configuration" link: "configuration" --- # Rendering Images Images are stored as originals in the Alchemy picture library. Editors assign these images to [Picture ingredients](ingredients#picture) on elements. You control the rendering dimensions, cropping, and output format through ingredient settings and rendering options. Alchemy supports two storage adapters: **ActiveStorage** and **Dragonfly**. Both use the same rendering API, so you can switch between them without changing your templates. ## Configuration Image rendering is configured in your initializer. See the [Configuration guide](configuration#images) for all available options. ~~~ ruby # config/initializers/alchemy.rb Alchemy.configure do |config| config.output_image_quality = 85 config.preprocess_image_resize = "1000x1000" config.image_output_format = "original" config.sharpen_images = false end ~~~ ::: tip You can override the output format and quality per ingredient by passing `format` and `quality` as [rendering options](#rendering-options). ::: ## Rendering In most cases, rendering a Picture ingredient through `el.render(:image)` in your element view is all you need. It generates the correct image URL with all configured options applied. ~~~ erb <%# app/views/alchemy/elements/_article.html.erb %> <%= el.render(:hero_image) %> ~~~ The output depends on what the editor has configured on the picture: - **Image only** — renders an `<img>` tag - **With caption** — wraps in a `<figure>` with a `<figcaption>` - **With link** — wraps the image in an `<a>` tag - **With caption and link** — wraps the linked image in a `<figure>` with a `<figcaption>` ### Ingredient Settings Configure rendering in your `elements.yml` definition. ~~~ yaml - name: article ingredients: - role: hero_image type: Picture settings: size: 1200x600 crop: true ~~~ See the [Picture ingredient settings](ingredients#picture) for the full list of options. ### Rendering Options These options control how the image variant is generated. #### size `String` The dimensions to resize the image to, preserving aspect ratio. Example: `"400x300"`. #### crop `Boolean` Crop the image to exactly match the given size. #### format `String` The output format: `"jpg"`, `"png"`, `"gif"`, or `"webp"`. #### quality `Integer` The quality of rendered JPEG and WebP images. #### upsample `Boolean` Allow the image to be scaled up beyond its original dimensions. By default, images are only scaled down. ## Responsive Images Use the `srcset` and `sizes` ingredient settings to generate responsive `<img>` tags with multiple sources. ~~~ yaml - role: photo type: Picture settings: size: 1200x800 crop: true srcset: ['400x267', '800x533', '1200x800'] sizes: ['(max-width: 600px) 400px', '(max-width: 900px) 800px', '1200px'] ~~~ This renders an `<img>` tag with a `srcset` attribute containing URLs for each size and a `sizes` attribute telling the browser which source to use at each viewport width. ### Formats <Badge type="tip" text="8.4+" /> Since Alchemy 8.4, the `format` setting converts the image into another format, and `srcset` entries can be a hash with a `size` and their own `format`. ~~~ yaml - role: photo type: Picture settings: format: webp srcset: - 400x267 - size: 800x533 format: png - size: 1200x800 format: webp ~~~ The main image and the `400x267` entry are rendered as WebP because of the `format` setting, the `800x533` entry as PNG. A format is only applied if the picture can be converted, so SVG files are rendered as they are. All entries end up in the `srcset` of a single `<img>` tag, where the browser chooses by width only. Entries with the same size in different formats do not act as fallbacks for each other. To offer a format with a fallback, render a `<picture>` element with one `<source>` per format yourself, using `ingredient.picture_srcset_options`, which returns the `url`, `desc`, `width`, `height` and mime `type` of each entry. ::: tip New formats create new image variants. Generate them upfront instead of on the first request with: ~~~ bash bin/rake alchemy:generate:ingredient_picture_thumbnails ~~~ Set `ELEMENTS=element1,element2` to only generate thumbnails for some elements and `ASYNC=true` to enqueue them as background jobs (ActiveStorage only). ::: ## Remote Storage In production you typically want to store images on a remote service like Amazon S3, Google Cloud Storage, or Azure Storage instead of the local filesystem. **With ActiveStorage**, configure your storage service in `config/storage.yml` and set the active service in your environment config, just like any Rails application. See the [Active Storage Overview](https://guides.rubyonrails.org/active_storage_overview.html#setup) in the Rails guides. ~~~ yaml # config/storage.yml amazon: service: S3 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> region: eu-central-1 bucket: my-app-production ~~~ ~~~ ruby # config/environments/production.rb config.active_storage.service = :amazon ~~~ **With Dragonfly**, use the [alchemy-dragonfly-s3](extensions#alchemy-dragonfly-s3) extension to store images on S3 compatible services. ::: tip See [Extensions](extensions) for all available storage add-ons. ::: ## Caching Alchemy caches rendered image variants automatically. The caching strategy depends on your storage adapter. **ActiveStorage** uses Rails' built-in variant caching. Variants are generated on first request and stored alongside the original blob. **Dragonfly** uses Alchemy's `PictureThumb` model to cache rendered variants in the database. Three admin thumbnail sizes are pregenerated on upload. In both cases, adding a CDN in front of your application is recommended for production to avoid hitting the application server for repeated image requests. --- URL: "/seeder.html" LLMS_URL: "/seeder.md" prev: false next: false --- # Seeder The seeder creates pages, layout pages, and users from YAML files. Use it to set up a consistent page structure across environments or to bootstrap a fresh database. ## Setup The installer adds `Alchemy::Seeder.seed!` to your `db/seeds.rb` automatically. If you skipped the installer or need to add it manually: ~~~ ruby # db/seeds.rb Alchemy::Seeder.seed! ~~~ Then run: ~~~ bash bin/rails db:seed ~~~ ## Seeding Pages Create a `db/seeds/alchemy/pages.yml` file with your page hierarchy. The file must contain exactly one root content page. Nest children under the `children` key: ~~~ yaml # db/seeds/alchemy/pages.yml - name: Homepage page_layout: index children: - name: About Us page_layout: standard - name: Blog page_layout: blog children: - name: First Post page_layout: blogpost public_on: 2025-01-15 - name: Contact page_layout: contact ~~~ Each entry accepts any `Alchemy::Page` attribute. The most common ones: | Attribute | Description | |-----------|-------------| | `name` | Page name shown in the admin | | `page_layout` | Must match a layout defined in `page_layouts.yml` | | `public_on` | Publish date (`Date` or `DateTime`). Omit for unpublished drafts | | `children` | Nested array of child pages | ### Layout Pages Layout pages (headers, footers, sidebars) are global pages not part of the content tree. Mark them with `layoutpage: true`: ~~~ yaml - name: Homepage page_layout: index children: - name: About Us page_layout: standard - name: Footer layoutpage: true page_layout: footer - name: Header layoutpage: true page_layout: header ~~~ ## Seeding Users Create a `db/seeds/alchemy/users.yml` file. Each entry is passed directly to your user model's `create!` method, so the attributes depend on your user model: ~~~ yaml # db/seeds/alchemy/users.yml - email: admin@example.com password: secret123 alchemy_roles: - admin - email: editor@example.com password: secret123 alchemy_roles: - editor ~~~ ::: warning User seeds are meant for development and staging environments. Do not commit real credentials. ::: ## Idempotent Behavior The seeder checks for existing data before creating records: - **Pages** — skips if any pages exist in the database - **Users** — skips if any users exist in the database To reseed, reset the database first: ~~~ bash bin/rails db:reset ~~~ ## Default Site and Language If no site or language exists when seeding pages, the seeder creates them from your [configuration](configuration#default-site-and-language). Make sure your initializer has these configured before seeding. --- URL: "/sites.html" LLMS_URL: "/sites.md" prev: false next: false --- # Sites Alchemy supports multiple websites from a single Rails application. Each site has its own domain, languages, and page trees. ## How Sites Work A site is identified by its host name. When a request comes in, Alchemy matches it against the configured hosts and serves the correct site's content. | Attribute | Description | |-----------|-------------| | **Name** | Display name of the site | | **Host** | Primary domain (e.g. `www.example.com`) | | **Aliases** | Additional domains that serve the same site (space-separated) | | **Redirect to primary host** | When enabled, alias requests are redirected to the primary host | ## Setting Up Multiple Sites The Alchemy installer creates a default site. To add more sites, use the admin interface under the Sites module. Each site needs at least one [language](languages). After creating a new site, Alchemy will prompt you to create its default language. ## Single-Site Setups Most Alchemy installations use a single site. In this case, you can ignore the Sites module entirely — the default site created during installation handles everything. --- URL: "/tags.html" LLMS_URL: "/tags.md" prev: false next: false --- # Tags Tags help organize pictures and attachments in the [library](library), [pages](pages), and [taggable elements](elements#tagging). Editors can assign tags when uploading or editing media, and use them to filter the library. Alchemy uses [Gutentag](https://github.com/pat/gutentag) as its tagging engine. ## Managing Tags The Tags module in the admin interface lets you: - View all tags with their usage count - Rename tags - Merge duplicate tags into one - Delete unused tags ## Merging Tags When editing a tag, you can merge it into another existing tag. All pictures and attachments tagged with the old tag are re-tagged with the new one, and the old tag is removed. This is useful for cleaning up duplicates or consolidating related tags. --- URL: "/upgrading.html" LLMS_URL: "/upgrading.md" prev: text: "Deployment" link: "deployment" next: false --- # Updating Alchemy This guide describes how to update AlchemyCMS to a new version. <ServiceAd href="https://blish.cloud/en/solutions/alchemycms"> <template #before>Need help upgrading? Our sponsor</template> <template #after>offers professional <a href="https://blish.cloud/en/solutions/alchemycms" target="_blank">Alchemy upgrade services</a>.</template> </ServiceAd> ## Update the Gem Update the version constraint in your `Gemfile` and run bundler. ~~~ diff -gem 'alchemy_cms', '~> 8.0' +gem 'alchemy_cms', '~> 8.1' ~~~ If you use [alchemy-devise](extensions#alchemy-devise) for authentication, update it as well. The major and minor versions of `alchemy_cms` and `alchemy-devise` match. ~~~ diff -gem 'alchemy_cms', '~> 8.0' +gem 'alchemy_cms', '~> 8.1' -gem 'alchemy-devise', '~> 8.0' +gem 'alchemy-devise', '~> 8.1' ~~~ Then run bundler. ~~~ bash bundle update alchemy_cms alchemy-devise ~~~ ## Run the Upgrade Task The upgrade task runs all necessary migrations and adjustments. ~~~ bash bin/rails alchemy:upgrade ~~~ Follow the on-screen instructions. The task will inform you about changes and any manual steps required. If you have `alchemy-devise` installed, re-run its install generator to pick up any changes. ~~~ bash bin/rails g alchemy:devise:install ~~~ ### Run Individual Upgrade Tasks Upgrade tasks can also be run individually. For example, to run only the config upgrade: ~~~ bash bin/rails alchemy:upgrade:config ~~~ List all available upgrade tasks with: ~~~ bash bin/rails -T alchemy:upgrade ~~~ ## Follow-ups Most of the time the upgrader handles everything. But some upgrades require manual steps — these are listed as TODOs at the end of the upgrade output. Follow them carefully. ## Major Version Upgrades Major versions follow the same upgrade process, but include breaking changes. They remove deprecated features and old upgrade tasks. ::: warning Before upgrading to the next major version, make sure you have addressed all deprecation warnings and run all upgrade tasks in the current version first. ::: ## Upgrading in Production After deploying a new version of the `alchemy_cms` gem, database migrations run automatically via `db:prepare`. The `alchemy:upgrade` task bundles several individual upgrade tasks. Some of these (like data migrations) need to run in production, while others (like config or file changes) are meant for development only. Check the output of `bin/rails -T alchemy:upgrade` and run the relevant tasks individually in production as needed. ## Verify the Upgrade After upgrading: 1. Review the changes with `git diff` 2. Run your test suite 3. Check the [CHANGELOG](https://github.com/AlchemyCMS/alchemy_cms/blob/main/CHANGELOG.md) for details on what changed --- URL: "/v6" LLMS_URL: "/v6.md" layout: "home" hero: name: "AlchemyCMS" image: src: "/icon.svg" alt: "AlchemyCMS Icon" text: "The open source content framework built in Rails" actions: - text: "Get Started →" link: "/v6/getting_started" theme: "brand" features: - title: "Not a common CMS" icon: "🚀" details: "Alchemy is a CMS framework. It enables you to build your own CMS." - title: "Highly customizable" icon: "🛠" details: "Build even complex websites in a shorter time frame." - title: "Easy to use" icon: "😇" details: "The user interface is powerful, easy to use and intuitive to learn." --- <h2 style="text-align: center">Sponsored by</h2> <div style="text-align: center; margin: 2rem 0; display: flex; justify-content: center; align-items: center"> <a href="https://blish.cloud" target="_blank"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 117.1 25.5167" width="117.1" height="auto" class="blish-logo" > <path fill="currentColor" d="m117.324.644h-5.457v24.7h5.457zm-16.272 15.011h6.373v-4.679h-6.373v-10.332h-5.352v24.7h5.348zm-27.852-8.49c0-1.172 1.241-2.019 3.26-2.019a5.191 5.191 0 0 1 4.462 2.128l3.654-2.906a9.658 9.658 0 0 0 -8.256-4.108c-4.541 0-8.51 2.551-8.51 6.983 0 3.969 2.443 5.88 5.989 7.161 3.437 1.281 6.201 1.813 6.201 3.97 0 1.172-.955 2.443-3.684 2.443a6.961 6.961 0 0 1 -6.205-3.26l-3.969 2.728c1.527 2.975 4.817 5.457 10.135 5.457 6.274 0 9.18-3.191 9.18-7.545 0-3.546-1.842-5.634-6.3-7.338-4.009-1.577-5.95-1.646-5.957-3.694m-22.1 18.182h5.457v-24.703h-5.457zm-24.5 0h15.1v-4.964h-9.635v-19.739h-5.457zm-10.433-7.308a5.805 5.805 0 0 0 -4.967-6.127 4.969 4.969 0 0 0 3.152-4.964c0-3.654-3.122-6.3-7.052-6.3h-7.05v4.675h6.215a2.512 2.512 0 0 1 2.62 2.4v.138a2.534 2.534 0 0 1 -2.453 2.62h-6.382v4.219h7.279a2.959 2.959 0 0 1 3.211 2.689 1.6 1.6 0 0 1 .01.217 3.027 3.027 0 0 1 -3.044 3.014h-4.8v.02h-2.656v4.7h8.412a7.211 7.211 0 0 0 7.506-6.9 2.63 2.63 0 0 0 0-.394" /> </svg> </a> </div> --- URL: "/v6/about.html" LLMS_URL: "/v6/about.md" prev: "getting_started" next: "pages" --- # About AlchemyCMS Alchemy is a user friendly and flexible Rails based CMS. In this guide you will learn about Alchemy's powerful features: * The flexible content storing architecture * The image processing magick * Caching * The resources controller ## Rails Engine AlchemyCMS is a Ruby on Rails engine. Read about [engines in the Rails guides](https://guides.rubyonrails.org/engines.html). ## The Core modules AlchemyCMS comes with six core modules: 1. [Pages](pages) 2. Languages 3. Sites 4. Users 5. Tags 6. Library (Pictures & Attachments) The core modules can be extended by own modules. A module should be a Rails engine that registers itself at Alchemy. Read more about [creating own modules](how_to_create_modules). ## Storing architecture Unlike many other CMS's that store a whole page body with complete HTML markup, Alchemy only stores unformatted text, ids of objects (like attachments and pictures) and only some richtext content in the database. No HTML markup, no CSS, no styling, no layout. Just pure content. You, the webdeveloper are in full control of the markup and styling. The editor just manages the content. ### The render flow Alchemy uses [Rails' partials](https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials). It has no own templating language and no *special files*. When Alchemy renders a typically Page, these files get usually rendered: 1. `app/views/layouts/application.html.erb` 2. `app/views/alchemy/page_layouts/_<page_layout>.html.erb` 3. `app/views/alchemy/elements/_<element>.html.erb` 4. `app/views/alchemy/ingredients/_<ingredient>_view.html.erb` ::: tip Alchemy comes with useful helpers that help render these partials. For further information please [have a look into the Alchemy::PagesHelper documentation](https://www.rubydoc.info/github/AlchemyCMS/alchemy_cms/Alchemy/PagesHelper.html) ::: ### Tips for page and content structure When working with AlchemyCMS the very first thing you should do is identify the website´s layout different page types (called page layouts). Every page which is structurally different to another, should have its own page layout. A page layout is a HTML template with specified properties. [More about pages and their layouts »](pages.html#defining-page-layouts) After that you should identify fixed areas on the layout and define them for sidebars, heroes and other groups of content. Fixed elements render on page layouts and are acting as containers for element groups. [More about fixed elements »](elements#render-a-group-of-elements-on-a-fixed-place-on-the-page) In any case you should split the layout into elements. That means grouping small parts of the websites content into reusable containers. Elements are rendered on page layouts and are the key component of Alchemy. [More about elements »](elements) An ingredient is the smallest part of content in Alchemy and represents a headline, an image, paragraphs and other values of content. [More about ingredients »](ingredients) ## Processing images Alchemy uses [ImageMagick®](https://www.imagemagick.org) and [Dragonfly](https://markevans.github.io/dragonfly/) to render images on-the-fly. Images are stored as master images in the picture library. The editor just assigns these images to elements you provide them. You also set the image rendering boundaries. The max width and heigth values, or even the size an image should be cropped to. Alchemy comes with a built-in image cropping tool, so that the editor can define the mask to be used. [Read more about how to use the powerful image processing tools](render_images). ## Caching If not deactivated in the [configuration](configuration), Alchemy uses Rails "Russian Doll Caching" for fast content delivering. This will cache all [page layout](pages) partials where [elements](elements) are rendered on. With every page request Alchemy sends cache headers that are used by CDNs, proxies and your vistors browser to cache the page body. ## Rails admin for your custom models Alchemy can also be your Rails admin for your own custom models and controllers. When developing own modules for Alchemy, your controller can inherit from that resources controller (`Alchemy::Admin::ResourcesController`) to get typical Rails CRUD methods. The standard templates for the backend views are also rendered. They can be overwritten in your app, just use the expected filenames and variables. Read more details about the resources controller in the [module development](how_to_create_modules) guide. --- URL: "/v6/configuration.html" LLMS_URL: "/v6/configuration.md" prev: "render_images" next: "upgrading" --- # Configuration of AlchemyCMS Alchemy configuration is done in the `config/alchemy/config.yml` file. Use ~~~ bash bin/rake alchemy:install ~~~ or ~~~ bash bin/rails g alchemy:install ~~~ to generate it. ## The configuration file Please [have a look into the source code](https://github.com/AlchemyCMS/alchemy_cms/blob/master/config/alchemy/config.yml). The file is pretty well documentated and much more up to date then this guide. ## Environment related configurations Beside using that one configuration file you also can seperate certain config settings for specific environments. For example: You want to enforce SSL in production, while in your development environment you don't. Its easy to achieve by setting up a second configuration file for development which contains nothing but the ssl enforcing setting. Your apps default config: ~~~ your_app/config/alchemy/config.yml ~~~ Environment specific config: ~~~ your_app/config/alchemy/development.config.yml ~~~ ::: tip NOTE When running your application in development mode both config files get merged, while the environment specific config takes precedence over the default config. ::: --- URL: "/v6/elements.html" LLMS_URL: "/v6/elements.md" prev: "pages" next: "ingredients" --- # Elements ## Overview Elements are reusable blocks of content that belong together, visually and contextually. Imagine you combine a headline, a text block and a picture - this is a typical element, maybe you want to call it an "article". Some people speak of widgets, blocks or components. All these are elements in Alchemy. Elements can be nested into each other and are the key of Alchemy's flexible content architecture. They give you a powerful tool to build the CMS you need. The ingredients of an element are of a specific [type](ingredients). Each ingredient represents a data type that store values in the database. ## Templates Each element has a template (they are called [partials in Rails](https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials)). They live in `app/views/alchemy/elements/`. ::: tip NOTE You don't need to create the files yourself. Use [the built in generator](#generating-the-partials) to let Alchemy generate them for you. ::: ## Defining elements Elements are defined in the `config/alchemy/elements.yml` file. Element definitions are written in [YAML](http://yaml.org) If this file does not exist yet, use the scaffold generator to do that now: ~~~ bash bin/rails g alchemy:install ~~~ The generator also creates all the other basic folders and files for setting up your website with Alchemy. ::: warning NOTE The element definitions are cached. Please restart the server after editing the `elements.yml`. ::: ### Example element definition ~~~ yaml # config/alchemy/elements.yml - name: article unique: true ingredients: - role: image type: Picture - role: headline type: Text as_element_title: true - role: copy type: Richtext ~~~ The element in this example is named **"article"** and can be placed only once per page (because it is `unique`). It has three ingredients of the following types: * [Picture](ingredients.html#picture) * [Text](ingredients.html#text) * [Richtext](ingredients.html#richtext) ::: tip You can select which ingredient will be used for the preview text in the element's title bar in the admin frontend by adding `as_element_title: true` to the desired ingredient. In the example above, the **"headline"** ingredient would be used. ::: ### Element settings The following settings can be used to define elements in the `elements.yml`. * **name** `String` _required_ A lowercased **unique** name of the element. Separate words need to be underscored. The name is used in the `page_layouts.yml` file to define on which pages the element can be used. It is also part of the `app/views/alchemy/elements` view partials file names. The name is [translatable](#translating-elements) for the user in the admin frontend. * **unique** `Boolean` (Default: `false`) Passing `true` means this element can be placed only once on a page. * **hint** `String` A hint for the user in the admin interface that should be used to describe what the element is used for. The hint is [translatable](#translating-elements) if you provide an I18n translation key instead of a complete sentence. The hint itself will be displayed as a small question mark icon and will reveal a tooltip once hovered by the user. * **message** `String` A prominent informational message displayed at the top of the element editor form in the admin interface that can be used to give your user additional information. You can even use simple html to add some emphasis to your message. * **warning** `String` A prominent warning message displayed at the top of the element editor form in the admin interface that can be used to warn your user about something. You can use simple html to add even more emphasis to your warning. * **nestable_elements** `Array` A collection of element names that can be nested into the element. * **taggable** `Boolean` (Default: `false`) Enables the element to be taggable by the user in the admin frontend. * **fixed** `Boolean` (Default: `false`) Used to separate an element from the normal flow. When `true`, the element is rendered on the page only with the explicit call of the `fixed_elements` scope. See [fixed elements](elements.html#render-a-group-of-elements-on-a-fixed-place-on-the-page) for more details. * **ingredients** `Array` A collection of ingredients the element contains. A ingredient has to have a `role` (unique per element) and a `type`. ::: tip Have a look at the [ingredients guide](ingredients) to get more informations about available ingredient types. ::: In the following examples you will see how to use these settings. ## Nestable elements You are able to nest elements into other elements. Just define nestable elements in your `elements.yml` file. ### Example ~~~ yaml - name: article ingredients: - role: headline type: Text nestable_elements: - text - picture - name: text ingredients: - role: text type: Richtext - name: picture ingredients: - role: text type: Picture ~~~ ::: warning NOTE Nested elements can also have `nestable_elements`. Just don't get too crazy with it, though. ::: ## Rendering nested elements Use the `render` helper to render all nested elements as a collection. ~~~ erb <%= element_view_for(article) do |el| %> <h3><%= el.render :headline %></h3> <div class="text-blocks"> <%= render article.nested_elements %> </div> <% end %> ~~~ Or `render` a group of nested elements by filtering the `nested_elements` collection by element name. ~~~ erb <%= element_view_for(article) do |el| %> <h3><%= el.render :headline %></h3> <div class="text-blocks"> <%= render article.nested_elements.named(:text) %> </div> <div class="pictures"> <%= render article.nested_elements.named(:picture) %> </div> <% end %> ~~~ Or `render` a single nested element by loading it from the `nested_elements` collection by element name. ~~~ erb <%= element_view_for(article) do |el| %> <h3><%= el.render :headline %></h3> <div class="picture"> <%= render article.nested_elements.find_by(name: :picture) %> </div> <div class="text-blocks"> <%= render article.nested_elements.where(name: :text) %> </div> <% end %> ~~~ ## Element with tags Elements are taggable. To enable it, add `taggable: true` to the element's definition. ~~~ yaml - name: article taggable: true ingredients: - role: image type: Picture - role: headline type: Text as_element_title: true - role: copy type: Richtext ~~~ Tags are a collection on the `element` object. `element.tag_list` returns an array of tag names. ~~~ erb <%= element.tag_list.join(', ') %> ~~~ Alchemy uses the [gutentag](https://github.com/pat/gutentag) gem, so please refer to the github [README](https://github.com/pat/gutentag/blob/master/README.md) or the [Wiki](https://github.com/pat/gutentag/wiki) for further informations. ## Element with ingredient validations You can enable validations for your ingredients. They behave like the Rails model validations. Supported validations are * `presence` * `uniqueness` * `format` The `format` validator needs to have a [regular expression](http://rubular.com) or a predefined matcher string as its value. There are already predefined format matchers listed in the `config/alchemy/config.yml` file. It is also possible to add own format matchers there. ### Format matchers ~~~ yaml # config/alchemy/config.yml format_matchers: email: !ruby/regexp '/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/' url: !ruby/regexp '/\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix' ~~~ ### Example ~~~ yaml - name: person ingredients: - role: name type: Text validate: - presence: true - role: email type: Text validate: - format: email - role: homepage type: Text validate: - format: !ruby/regexp '/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/'] ~~~ The `email` ingredient gets validated against the predefined `email` matcher in the `config.yml`. The `homepage` ingredient is matched against the given regexp. ### Multiple validations. Ingredients can have multiple validations. ~~~ yaml - name: person ingredients: - role: name type: Text validate: - presence: true - uniqueness: true - format: name ~~~ The validations are evaluated in the order as they are defined in the list. At first the name ingredient will be validated for `presence`, then for `uniqueness` and at least against its `format`. ## Assign elements to page layouts Before you can use elements on pages, you need to define on which page layouts your element can be placed. So open `config/alchemy/page_layouts.yml` in your text editor and put the name of your new element into the list of available elements for a specific page layout. ~~~ yaml - name: standard elements: [article] autogenerate: [article] ~~~ You can now place the article element on each page with page layout `standard`. All future created pages with page layout `standard` will automatically create the article element for you. ## Generating the partials After typing the line below in your terminal, the rails generator will create the elements editor and view files. ~~~ bash bin/rails g alchemy:elements --skip ~~~ The `--skip` flag command skips files that already exist Without the skip flag, the generator will prompt you about over-writing your element view partials to include the content changes. The `--force` flag will overwrite your element view partials automatically. ::: tip You can pass `--template-engine` or `-e` as an argument to use `haml`, `slim` or `erb`. The default template engine depends on your settings in your Rails host app. ::: The generator will create partials for each element in your `app/views/alchemy/elements` folder. According to the first example, the article element, the generator will create the `_article.html.erb` partial. The generator does not only create these files, it also generates the necessary code for you. Mostly you can take use of the that code and make it nifty by adding some CSS stylings. ## Render elements in your layout Now that the above 'article' element example is associated with the 'standard' page layout, the element can be rendered on that layout `app/views/alchemy/page_layouts/_standard.html.erb`. ~~~ erb ... <div class="row"> <%= render_elements %> </div> ... ~~~ This renders all elements from current page. ::: tip Pages must be published for Elements to be associated and rendered. If you aren't seeing content you created in the admin interface, make sure elements are saved and and you click the "Publish current page content" button from the edit page admin view. ::: ### Render only specific elements Sometimes you only want to render specific elements on a specific page and maybe exclude some elements. ~~~ erb <body> <header><%= render_elements only: 'header' %></header> <main> <%= render_elements except: %w(header footer) %> </main> <footer><%= render_elements only: 'footer' %></footer> </body> ~~~ ### Render elements from other pages A common use case is to have global pages for header and footer parts: ~~~ yaml # config/alchemy/elements.yml - name: header hint: Navigation bar at the top of every page ingredients: # ... - name: footer hint: Footer section at the bottom of every page ingredients: # ... # config/alchemy/page_layouts.yml - name: header unique: true elements: [header] autogenerate: [header] layoutpage: true - name: footer unique: true elements: [footer] autogenerate: [footer] layoutpage: true ~~~ Which can be added to your `application.html.erb` file: ~~~ erb <!DOCTYPE html> <html lang="<%= @page.language_code %>"> <head> ... </head> <body> <header><%= render_elements from_page: 'header' %></header> <main> <%= yield %> </main> <footer><%= render_elements from_page: 'footer' %></footer> <%= render "alchemy/edit_mode" %> </body> </html> ~~~ ### Render a group of elements on a fixed place on the page Often you have a separate section on one page (like a sidebar) or a global section to be rendered on every page (like a navbar or footer). If you configure those elements as `fixed: true` in `elements.yml`, then they'll be separated from the general collection of elements and will be displayed separately in a separate tab in the admin elements section. ~~~ yaml {3} - name: sidebar unique: true fixed: true ingredients: - role: name type: Text # ... ~~~ You can then access these elements using the `fixed_elements` scope: ~~~ erb <% @page.fixed_elements.each do |element| %> <%= render_element(element) %> <% end %> ~~~ As `fixed_elements` is an a active record scope you can also filter by `where(name: 'your_element')` or use the `named('your_element')` scope. To gain some extra efficiency from Rails you could also use the collection rendering shortcut ~~~ erb <%= render @page.fixed_elements.named('sidebar') %> ~~~ ::: tip NOTE You need to use the elements view partial name instead of the element local variable in your child element views. Ie. `sidebar_view` instead of `element`. ::: ## Customizing the view partial The Alchemy element generator creates the basic html markup for you. Pretty useful, but maybe not what you need, sometimes. No problem, feel free to customize it. It's yours :). This is the newer notation for rendering the element's partial: ~~~ erb <%= element_view_for(element) do |el| %> <h3><%= el.render :headline %></h3> <div class="row"> <div class="large-6 columns"> <p> <%= el.render :image %> </p> </div> <div class="large-6 columns"> <p> <%= el.render :text %> </p> </div> </div> <% end %> ~~~ The `element_view_for` helper wraps the inner html code into a `div` element by default. You can pass arguments to the helper to change its rendering behavior: The second argument `tag` is used for the wrapping html tag. Passing `false` to it means no wrapping at all. Passing the name of any html element to it means the inner html gets wrapped within the given html tag instead of the default `div`. ### Pass options to the element wrapper You can pass additional arguments to add or change any html attributes of the wrapper. ~~~ erb <%= element_view_for(element, tag: 'li', class: 'red', id: 'my_unique_id') do |el| %> ... <% end %> ~~~ ::: tip If you want to learn more about the helper methods used in these partials, please have a look at the [Documentation](http://rubydoc.info/search/github/AlchemyCMS/alchemy_cms?q=helper). ::: ### Pass options to the ingredient view You can pass options to the ingredient view. ~~~ erb <%= element_view_for(element) do |el| %> <%= el.render :image, {size: '200x300'}, class: 'image-large' %> <% end %> ~~~ ::: tip Instead of passing the `size` of an image into the `EssencePicture` as shown above, you should consider to use static [ingredient settings](ingredients.html#individual-ingredient-settings) instead. ::: The first hash is the `options` the second one the `html_options` passed to the wrapper of the ingredient. If you only want to pass `html_options` you need to pass an empty hash as second argument. ~~~ erb <%= element_view_for(element) do |el| %> <%= el.render :image, {}, class: 'image-large' %> <% end %> ~~~ ::: tip Not all ingredients have wrapper tags. A list of all ingredient views are [here](https://github.com/AlchemyCMS/alchemy_cms/tree/master/app/views/alchemy/ingredients). ::: ## Translating elements Element and ingredient names are passed through Rails' [I18n](http://guides.rubyonrails.org/i18n.html) library. You can translate them in your `config/locales` language yml file. ~~~ yaml de: alchemy: element_names: contact_form: Kontaktformular search: Suche ingredient_roles: headline: Überschrift ~~~ Content names can also be translated related to their element. This is useful for ingredients with the same name that should have different translations. ~~~ yaml de: alchemy: element_names: contact_form: Kontaktformular ingredient_roles: color: Farbe contact_form: color: Button Farbe ~~~ ### Re-arranging Elements on a Page Collapsed elements can be re-arranged by clicking and dragging the its icon to the left of the element title. You can't re-arrange an expanded element, you need to collapse it first. ### Using the Clipboard The clipboard receives elements that are copied or cut. The most prominent use-case is to copy or move elements from one page to another. You can use the icons to copy and cut a specific element from its expanded state. You can view the contents of the clipboard using the icon at the top of the elements bar. When adding a new top-level element, the "Paste from clipboard" tab will be visible if the clipboard is storing any elements. --- URL: "/v6/getting_started.html" LLMS_URL: "/v6/getting_started.md" prev: false next: "about" --- # Getting Started This guide covers getting up and running with AlchemyCMS. After reading it, you should be familiar with: * Installing and creating a new application with AlchemyCMS * Creating a user and logging in for the first time. ## Prerequisites This guide is designed for beginners who want to get started with AlchemyCMS from scratch. It does not assume that you have any prior experience with AlchemyCMS. However, to get the most out of it, you need to have some prerequisites installed: * The [Ruby](http://www.ruby-lang.org/en/downloads) programming language * At least one of these databases: [MySQL](http://www.mysql.com/downloads/mysql) or [PostgreSQL](http://www.postgresql.org/download) * The image processing library [ImageMagick](http://www.imagemagick.org/script/install-source.php) ### Installing dependencies **It is highly recommended** to use a package manager to install dependencies like ImageMagick and your database. Common package managers are: * [Homebrew](http://brew.sh) on the Mac * Your Linux box already has a package manager installed and you likely are already familiar with it. ### Installing Ruby The most common ways to install Ruby on your computer nowadays are: * If you are on a Mac or Linux Box use [RVM](http://rvm.io) * If you are on Windows use [RailsInstaller](http://railsinstaller.org) ### Learning Rails It is highly recommended to familiarize yourself with Ruby on Rails before using AlchemyCMS. You will understand much more and see things clearer if you know about the basics of Ruby on Rails. There are many excellent resources on the internet for learning Ruby on Rails, including: * [Try Ruby](http://tryruby.org) * [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html) * [Rails for Zombies](http://railsforzombies.org) * [Railscasts](http://railscasts.com) ## Installing Alchemy ### Create the Rails application The installation of AlchemyCMS is very easy. You just need to run Ruby's `gem` command. ~~~ bash gem install alchemy_cms ~~~ Alchemy is a Rails 4 engine, so at first you need to generate a fresh Rails 4 application, by running this command ~~~ bash rails new YOUR_APP_NAME ~~~ ::: tip INFO The `rails` command has lots of parameters like choosing the database you want to work with. Please follow [the official Rails guides](https://guides.rubyonrails.org/getting_started.html) for further information. ::: ### Install Alchemy into the Rails application In your existing Rails application, you can need to require the AlchemyCMS gem. Just add this line to your `Gemfile`. ~~~ ruby gem 'alchemy_cms' ~~~ Since AlchemyCMS is a mountable engine, you need to define the mountpoint in your `config/routes.rb` file. ~~~ ruby MyApp::Application.routes.draw do mount Alchemy::Engine => '/' end ~~~ ::: tip You can mount Alchemy on every route you want. `pages`, `cms`, what ever you want. Most of the time you go with the root route. ::: ::: warning NOTE Please be aware that Alchemy has a very strong catch all route. It is *highly recommended* that you mount Alchemy at last position in your app, so your existing routes are still available. ::: Now you need to run bundler for installing the dependencies. ~~~ bash bundle install ~~~ Then run the install task. ~~~ bash bin/rake alchemy:install ~~~ #### Authentication If you already have your own user class in your application, you have to tell Alchemy about it. Please follow [this guide](how_to_add_custom_authentication) to learn how to achieve that. If you don't have a authentication solution, you can use the alchemy-devise gem. Just add it to your apps `Gemfile` ~~~ ruby # Gemfile gem 'alchemy-devise' ~~~ ~~~ bash bundle install ~~~ and run the installer ~~~ bash bin/rails g alchemy:devise:install ~~~ ## Starting AlchemyCMS Now that you have AlchemyCMS successfully installed, let's move on by creating your first user with administrative privilegs. You just need to start a local ruby server on your development machine. ~~~ bash cd YOUR_APP_NAME ~~~ ~~~ bash bin/rails s ~~~ Open a browser window and navigate to [http://localhost:3000](http://localhost:3000). You will be greeted with a screen that is prompting you to create the first user. Congratulations, you can now access the backend. --- URL: "/v6/how_to_add_custom_authentication.html" LLMS_URL: "/v6/how_to_add_custom_authentication.md" --- # Custom Authentication Alchemy 3.0 has removed its build in authentication into a separate gem called [alchemy-devise](https://github.com/AlchemyCMS/alchemy-devise). Now, that this is not in the core anymore it gives you the possibility to add your own custom authentification. You only have to tell Alchemy about your user class. In this guide you will learn to: * add just enough code to make your User class work with Alchemy * add additionally methods to enhance the integration ## Activating your model Alchemy has some defaults for user model name and login logout path names: ### Defaults * `Alchemy.user_class_name` defaults to `'User'` * `Alchemy.login_path` defaults to `'/login'` * `Alchemy.logout_path` defaults to `'/logout'` Anyway, you can tell Alchemy about your authentication model configuration. ### Example ~~~ ruby # config/initializers/alchemy.rb Alchemy.user_class_name = 'Admin' Alchemy.login_path = '/auth/login' Alchemy.logout_path = '/auth/logout' ~~~ ## Mandatory configuration ### Relationships - `has_many :folded_pages` - This will allow alchemy to store page tree folding on a per user basis. ### Setting roles The only method that AlchemyCMS needs from your user class is `alchemy_roles`. This method has to return an array of strings containing at least one of these roles: * `member` * `author` * `editor` * `admin` ### Example ~~~ ruby # app/models/user.rb def alchemy_roles if admin? %w(admin) else [] end end ~~~ ::: tip You can use your own authorization system to set the role. You only need to ensure that it returns an Array with at least one of the roles. ::: That's it. Alchemy has everything it needs to use your user class as authentication model. ## Optional methods Although Alchemy does not need much to know about your user class, there are some few other attributes that are good to have in your user class which are: ### Name This would be used to say "Welcome back `#{@user.name}`" in the Alchemy dashboard. ### Example ~~~ ruby # app/models/user.rb def name # If you don't have a name, you could use the user's email read_attribute(:email) end ~~~ ### Display name This is used in the "logged in as" note on the top right corner of the admin interface. ### Example ~~~ ruby # app/models/user.rb def alchemy_display_name # If you don't have a name, you could use the user's email "#{firstname} #{lastname}".strip end ~~~ ## Preferred Language A locale String to set the users preferred translation of the Alchemy GUI. Anyway, the user has always the option to switch the language, but it is good to give your user its preferred language. ### Example ~~~ ruby # app/models/user.rb def language # Always use dutch as translation 'nl' end ~~~ ::: tip In a real world application you maybe want to store this as `alchemy_language` in the users table. ::: ## User stamp columns Alchemy stores updater and creator ids (if the columns are present). If you want to track which user updated a record you need to add: `creator_id` and `updater_id` to your users table. ### Example ~~~ bash bin/rails g migration add_creator_id_and_updater_id_to_users creator_id:integer:index updater_id:integer:index bin/rake db:migrate ~~~ ## Tagging If you want your users to be taggable you need to add the `acts_as_taggable` and `acts_as_tagger` class methods. ### Example ~~~ ruby class User < ActiveRecord::Base acts_as_taggable acts_as_tagger ... end ~~~ ::: tip You also should add a `cached_tag_list` text column into your users table for better performance. ::: ~~~ bash bin/rails g migration add_cached_tag_list_to_users cached_tag_list:text ~~~ This is it! Now your user class should be a first class citizen of the Alchemy admin backend. --- URL: "/v6/how_to_create_a_blog_template.html" LLMS_URL: "/v6/how_to_create_a_blog_template.md" --- # How To: Create a blog template A blog is an appropriate way to present information about formal and informal events or extraordinary experiences. In this guide you will learn how to: - Define blog layout and elements - Integration in your existing application ## Conceptual page and content structuring Imagine you like to have a simple blog template on your website. First of all as mentioned in [about AlchemyCMS](about) you should identify required components in your blog layout. Common components are a **headline**, a **blog post** itself and **sidebar blocks** for category overview and featured posts. Following the steps you will create blog template according to [Foundation Zurb](http://foundation.zurb.com/). (© ZURB, Inc) *We will build this template using the [Blog sample](http://foundation.zurb.com/page-templates/blog.html)* ## Concept and structure Visit the Blog sample page and have a deep look at the layout. Let us start to divide the template. We can identify the following page elements: - **blog post (multiple)** contains title, date, author, image and text - **categories block** contains list of categories - **featured block** contains text block with link to favorite blog post ## Prerequisites At the beginning some preparation is required. First of all, follow these instruction provided by Zurb to [Add Foundation to your Rails app](http://foundation.zurb.com/docs/applications.html). Hence the necessary preparations are completed. ## Define elements First of all we define the elements we need in `config/alchemy/elements.yml`. The individual page elements (see [Concept and structure](#concept-and-structure)) could be considered as an element. ### Blog title The blog should have a unique title. The title element contains a `title` and a `text` which represents a description or motto. In both cases we choose an `Text`, due to the fact that the `title` and the `text` will contain simple text. The `default` attribute provides the preset value, if the user didn't specify a motto or description (in Alchemy's backend). ~~~ yaml - name: blog_title unique: true ingredients: - role: title type: Text default: Blog - role: text type: Text default: This is my blog. It's awesome. ~~~ ### Blog post A `blog_post` element has a title, date, author, image and text. The title is represented by `blog_post_title`. It's just a text, so we took `Text` as type. Below `settings` we use `linkable: true`, because the user should be able to choose a link for the title of the blog post. The meta data like author and creation date corresponds to `blog_post_meta`. The `blog_post_intro_text` text as well as the main content `blog_post_content` get the type `Richtext`, since the user likes to add multiline paragraphs. The `blog_post_image` is of type `Picture`. This will empower the user to add an image. ~~~ yaml - name: blog_post ingredients: - role: blog_post_title type: Text default: Blog Post Title settings: linkable: true - role: blog_post_meta type: Text default: "Written by John Smith on August 12, 2012" - role: blog_post_intro_text type: Richtext - role: blog_post_image type: Picture - role: blog_post_content type: Richtext ~~~ ### Categories Block The 'categories block' has a headline and a list of available categories. The `categories_headline` is represented by an `Text`. In the backend the user should be able to add categories easily and dynamically. That's the reason why we chose the `nestable_elements` feature. ~~~ yaml - name: categories_block ingredients: - role: categories_headline type: Text default: Categories nestable_elements: - category - name: category ingredients: - role: name type: Text settings: linkable: true ~~~ ### Featured block The 'featured block' with a headline, text and a link. ~~~ yaml - name: featured unique: true ingredients: - role: featured_headline type: Text default: "Featured" - role: featured_text type: Richtext - role: featured_link type: Text settings: linkable: true ~~~ ::: tip Instead of `Text` you could alternatively use `Link` for 'featured_link'. ::: ## Define blog page layout After the definition of the elements, you we will continue with the defintion of an appropriate page layout. Page layout are specified in `config/alchemy/page_layouts.yml`. Add a new page layout for your blog template: ~~~ yaml - name: blog elements: - blog_title - blog_post - categories_block - featured autogenerate: - blog_title ~~~ The `autogenerate` attribute allows to define elements which will be generated automatically, when the user creates a new page in backend. ## Generate and customize the partials The command ~~~ bash rails g alchemy:elements --skip ~~~ creates the partials according to the element definition in the `elements.yml`. The partials are stored in `app/views/alchemy/elements/`. e.g. the partial for 'blog_post' element: `_blog_post.html.erb` ### Customize the partials In order to adapt the output of the elements you have to change the partial. The command above generates default partials. The `_blog_post.html.erb` might look like this: ~~~ erb <% cache(blog_post) do %> <%= element_view_for(blog_post) do |el| %> <div class="blog_post_title"> <%= el.render :blog_post_title %> </div> <div class="blog_post_meta"> <%= el.render :blog_post_meta %> </div> <div class="blog_post_intro_text"> <%= el.render :blog_post_intro_text %> </div> <div class="blog_post_image"> <%= el.render :blog_post_image %> </div> <div class="blog_post_content"> <%= el.render :blog_post_content %> </div> <% end %> <% end %> ~~~ #### `_blog_post_.html.erb` The blog post should match with the layout of the blog page in general, therefore we edit the partial: ~~~ erb <% cache(blog_post) do %> <%= element_view_for(blog_post) do |el| %> <article> <h3><%= el.render :blog_post_title %></h3> <h6><%= el.render :blog_post_meta %></h6> <div class="row"> <div class="large-6 columns"> <%= el.render :blog_post_intro_text %> </div> <div class="large-6 columns"> <%= el.render :blog_post_image %> </div> </div> <%= el.render :blog_post_content %> </article> <hr/> <% end %> <% end %> ~~~ For the remaining elements replace the content of the correspondig partials with the following code snippets: #### `_blog_title.html.erb` ~~~ erb <% cache(blog_title) do %> <%= element_view_for(blog_title, tag: 'h1') do |el| %> <%= el.render :title %> <small><%= el.render :text %></small> <% end %> <% end %> ~~~ #### `_categories_block.html.erb` ~~~ erb <% cache(categories_block) do %> <%= element_view_for(categories_block) do |el| %> <h5><%= el.render :categories_headline %></h5> <%= render categories_block.nested_elements %> <% end %> <% end %> ~~~ #### `_category.html.erb` ~~~ erb <% cache(category) do %> <%= element_view_for(category, tag: 'p') do |el| %> <%= el.render :name %> <% end %> <% end %> ~~~ #### `_featured.html.erb` ~~~ erb <% cache(featured) do %> <%= element_view_for(featured) do |el| %> <h5><%= el.render :featured_headline %></h5> <%= el.render :featured_text %> <p><%= el.render :featured_link %></p> <% end %> <% end %> ~~~ ## Embed the elements into your page layout Finally you have to integrate your elements into the page layout. ### Create new page layout view Create a new file called `_blog.html.erb` in `app/views/alchemy/page_layouts` and copy the plain html code from [Foundation Zurb Blog Template Raw](https://gist.githubusercontent.com/ghaida/5054379/raw/f080e139a4e25c4548b265e7229cd6dbe09bff92/gistfile1.html). ### Edit blog page layout view In `app/views/alchemy/page_layouts/_blog.html` search for (inside 'Nav Bar' comment) ~~~ erb <h1> Blog <small>This is my blog. It's awesome.</small> </h1> ~~~ and replace with: ~~~ erb <%= render_elements only: 'blog_title' %> ~~~ The **blog_title** set in the backend is now visible in the blog page layout context. Inside the commented 'Main Blog Content' replace the whole content of `<div class="large-9 columns" role="content">` with ~~~ erb <%= render_elements only: 'blog_post' %> ~~~ to embed the **blog_post** elements into the page layout view. Inside 'Sidebar' comment replace ~~~ erb <h5>Categories</h5> <ul class="side-nav"> <li> <a href="#">News</a> </li> <li> <a href="#">Code</a> </li> <li> <a href="#">Design</a> </li> <li> <a href="#">Fun</a> </li> <li> <a href="#">Weasels</a> </li> </ul> ~~~ with ~~~ erb <%= render_elements only: 'categories_block' %> ~~~ and replace the content of `<div class="panel">` with ~~~ erb <%= render_elements only: 'featured' %> ~~~ Now you can run ~~~ bash rails server ~~~ to start the server. --- URL: "/v6/how_to_create_a_contact_form.html" LLMS_URL: "/v6/how_to_create_a_contact_form.md" --- # How To: Create a contact form This guide covers the configuration setup and create process of a regular contact form: - Setup Alchemy mailer - Customize the views ## Additional Rails and application setup Alchemy relies on Rails' [ActionMailer](https://guides.rubyonrails.org/action_mailer_basics.html) to send emails. You will need to configure ActionMailer to use an SMTP service like Gmail, Sendgrid, Mailgun, Mandrill, Amazon SES, etc. ## Mailer Settings This is the default mailer configuration in `config/alchemy/config.yml`: ~~~ yaml mailer: page_layout_name: contact forward_to_page: false mail_success_page: thanks mail_from: your.mail@your-domain.com mail_to: your.mail@your-domain.com fields: [salutation, firstname, lastname, address, zip, city, phone, email, message] validate_fields: [lastname, email] ~~~ - **page_layout_name** `String` (Optional) A `Alchemy::PageLayout` name. Used for rendering the contact form. - **forward_to_page** `Boolean` (Optional) If set to true the mailer redirects to the page defined with mail_success_page option. - **mail_success_page** `String` (Optional) A page urlname that should be displayed after successfully sending the mail. - **mail_from** `String` (Optional) The email address the mail is send from. - **mail_to** `String` (Optional) The email address the mail is send to. - **fields** `Array` Field names of your contact form. These fields become attributes on the `Alchemy::Message` Model - **validate_fields** `Array` Field names of your contact form that should be validated for presence. ::: tip NOTE All optional setting should be manageable through the content management user. So creating an element that set these values is **highly recommended**. ::: ## Creating a contact form element Describe a new Element with this options inside your `elements.yml` file: ~~~ yaml - name: contactform ingredients: - role: mail_from type: Text validate: - presence - format: email - role: mail_to type: Text validate: - presence - format: email - role: subject type: Text validate: - presence - role: success_page type: Page validate: - presence ~~~ ::: tip NOTE The fields `mail_to`, `mail_from`, `subject` and `success_page` are recommended. The `Alchemy::MessagesController` uses them to send your mails. That way your customer has full control over these values inside his contact form element. ::: ::: warning INFO The validations are optional, but **highly recommended**. ::: Create a page layout for your contact page in the `page_layouts.yml` file: ~~~ yaml - name: contact unique: true cache: false elements: - pageheading - heading - contactform autogenerate: - contactform ~~~ ::: warning INFO Disabling the page caching is **very important**! ::: ## Example contact form Use the `rails g alchemy:elements --skip` generator to create the view files. ### The contact form view We are using the great `simple_form` gem in this example. If this gem is not already installed, you have to add ~~~ bash bundle add simple_form ~~~ Then open `app/views/alchemy/elements/_contactform.html.erb` in your text editor and replace the content with: ~~~ erb <%= simple_form_for(@message ||= Alchemy::Message.new) do |form| %> <%= form.input :firstname %> <%= form.input :lastname %> <%= form.input :email %> <%= form.input :message, as: 'text' %> <%= form.hidden_field :contact_form_id, value: element.id %> <%= form.button :submit %> <% end %> ~~~ ::: warning INFO See the `hidden_field`? This is important, or the messages mailer can't do all the magic for you. ::: If you use different or additional input symbols like 'company' , 'age' etc. make sure to adapt the fields in the mailer configuration (`config/alchemy/config.yml`). ::: tip Please have a look at the [simple form documentation](https://github.com/plataformatec/simple_form#readme) for further infos about the various config options. ::: ## Translating validation messages All validation messages are passed through `::I18n.t` so you can translate it in your language yml file. ### Example Translation ~~~ yaml de: activemodel: attributes: alchemy/message: firstname: Vorname lastname: Nachname ~~~ If you would like to use same vocabulary in different context: e.g in the `app/views/alchemy/elements/_contactform_view.html.erb` ~~~ erb <%= form.input ... %> <%= form.input :firstname , label: t(:sender, scope: 'my_forms.contactform') %> <%= form.input ... %> ~~~ Your language yml should look like this: (`config/locales/de.yml`) ~~~ yaml de: my_forms: contactform: sender: Sender ~~~ Now you can reuse the label by using the `t` function while defining label. --- URL: "/v6/how_to_create_custom_ingredients.html" LLMS_URL: "/v6/how_to_create_custom_ingredients.md" --- # Creating own Ingredients Alchemys powerful content storage technology is build around ingredients. They are ActiveRecord models that have an editor and a presentation view partial. Alchemy has lots of [build in ingredients](https://github.com/AlchemyCMS/alchemy_cms/tree/main/app/models/alchemy/ingredients). But you can create your own ingredient and even associate them with your existing models. This guide shows how. * Use the generator to create a new ingredient * Associate an ingredient to your model ## Using The Ingredient Generator The ingredient generator is a wrapper around the Rails model generator. It generates the ingredient model for you. ~~~ bash bin/rails g alchemy:ingredient MyIngredient ~~~ ::: tip INFO Ingredients are created under the `Alchemy::Ingredients` namespace. ::: This is what the generated model looks like: ~~~ ruby # app/models/alchemy/ingredients/my_ingredient.rb module Alchemy module Ingredients class MyIngredient < Alchemy::Ingredient # Set additional attributes that get stored in the `data` JSON column # store_accessor :data, :some, :attribute # Set a related_object alias for convenience # related_object_alias :some_association_name, class_name: "Some::Klass" end end end ~~~ Alchemy stores the main value in the `value` (a `Text`) column. If you want to store additional values in another column, please add it as attribute to the `data` [`store_accessor`](https://api.rubyonrails.org/classes/ActiveRecord/Store.html). Rails will create accessor methods for you. ## The ingredient views Every ingredient has to have two views: * One for presenting * One for editing ### The view partial The view partial is used by the `render_elements` helper to present the ingredient to the user. It is yours. Adjust it to your needs. You can access the value with the `ingredient` method of the `content` object instance. ~~~ erb <!-- app/views/alchemy/ingredients/_my_ingredient_view.html.erb --> <h1><%= my_ingredient_view.ingredient %></h1> ~~~ ### The editor partial The editor partial is basically a set of form fields holding values of your ingredient. It is rendered inside the element editor view form object. This is just what the generator creates for you: ~~~ erb <!-- app/views/alchemy/ingredients/_my_ingredient_editor.html.erb --> <%= content_tag :div, class: my_ingredient_editor.css_classes, data: my_ingredient_editor.data_attributes do %> <%= element_form.fields_for(:ingredients, my_ingredient_editor.ingredient) do |f| %> <%= ingredient_label(my_ingredient_editor) %> <%= f.text_field :value %> <% end %> <% end %> ~~~ But this is yours. Feel free to adjust it to your needs. Just make shure that you provide form fields that Alchemy can use to update your object in the database. ## Associations You can associate every ActiveRecord based model with an ingredient. In this example we want to connect an existing `Person` model to an element, so we can associate it with an Alchemy page. Just use the `related_object_alias` method to tell Alchemy the foreign key to use for the association. ### Set the foreign key ~~~ ruby # app/models/alchemy/ingredients/person.rb module Alchemy module Ingredients class Person < Alchemy::Ingredient related_object_alias :person, class_name: "My::Person" end end end ~~~ ### Accessing your model instance That's it. Everything else is handled by Alchemy. You can now access the associated `My::Person` model with the `person` method. ~~~ erb <!-- app/views/alchemy/ingredients/_person_view.html.erb --> <%= person_view.person.firstname %> ~~~ Now you can use your new ingredient in any element you want as shown in the [Elements guide](elements). --- URL: "/v6/how_to_create_modules.html" LLMS_URL: "/v6/how_to_create_modules.md" --- # Creating custom Modules Basicly there are two ways to set up a module: * Use Alchemy as your Rails admin backend * Create a new Engine ## Generator Alchemy provides a rails generator for custom modules, that can be invoked like this ~~~ bash bin/rails generate alchemy:module your_module_name ~~~ ## Using Alchemy as your Rails admin backend Hence every module is a subsection of a Rails application or a gem you can roll out your own admin backend if you like. By using Alchemy's backend interface you get a whole bunch of things: 1. Authentication and Authorization 2. A highly and fine grained customizable resource manager (CRUD interface) 3. A nicely styled user interface And it's easy as this: ~~~ ruby module Admin class YourResourcesController < Alchemy::Admin::ResourcesController end end ~~~ You can overwrite the default controller actions and views as you like. An instance-variable named after the resource (i.e. `@your_resource` for `YourResource`) is defined for use in your views. After you setting up your routes you have to take care for control access: ## Authorization Rights and roles are set in a CanCan Ability model. Create the file if it doesn't exist already. It has to be registered manually, by invoking `Alchemy.register_ability MyModuleAbility` in the module initializer. ~~~ ruby # app/models/my_module_ability.rb class MyModuleAbility include CanCan::Ability def initialize(user) if !user.blank? && user.is_admin? can :manage, YourResource can :manage, :admin_your_resources end end end ~~~ More information about authorization can be found in the docs of the [`cancancan` gem](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities) ## Routing ### In an engine ~~~ ruby # config/routes.rb YourApp::Application.routes.draw do ... mount YourAlchemyModule::Engine => '/' mount Alchemy::Engine => '/' end ~~~ ### In your app ~~~ ruby # config/routes.rb YourApp::Application.routes.draw do resources :your_resource namespace :admin do resources :your_resource end mount Alchemy::Engine => '/' end ~~~ ::: tip Due to Alchemy's strong routes it is **strongly recommended** to mount your engine before Alchemy. ::: ## Registering the module Last but not least the module needs to be registered to AlchemyCMS. ### When using your host app ~~~ ruby # my_host_app/config/initializers/alchemy_modules.rb Alchemy::Modules.register_module({ name: 'name_of_your_module', order: 1, # The position in main navigation, if you have more than 1 module. navigation: { name: 'modules.products', # The name in the main navigation (translated via I18n). controller: '/admin/products', # The controller that will be used. action: 'index', # The controller action that will be used. icon: 'module_icon', # Class of icon that will be rendered as navigation icon. sub_navigation: [ { name: 'modules.products', # The name for the subnavigation tab (translated via I18n). controller: '/admin/products', # Controller that will be used. action: 'index' # Controller action that will be used. }, { name: 'modules.variants', # The name for the subnavigation tab (translated via I18n). controller: '/admin/variants', # Controller that will be used. action: 'index' # Controller action that will be used. } ] } }) # Register the module ability Alchemy.register_ability MyModuleAbility ~~~ NOTE: As an alternative to the module icon you can pass: `image: '/alchemy/icon_of_your_module.png'` ### Using a Rails engine ~~~ ruby # my_engine/config/initializers/alchemy.rb Alchemy::Modules.register_module({ name: 'name_of_your_module', engine_name: 'name_of_your_engine' # The engine_name set in your Rails::Engine class. order: 1, # The position in main navigation, if you have more than 1 module. navigation: { name: 'modules.products', # The name in the main navigation (translated via I18n). controller: '/admin/products', # The controller that will be used. action: 'index', # The controller action that will be used. icon: 'module_icon', # Class of icon that will be rendered as navigation icon. sub_navigation: [ { name: 'modules.products', # The name for the subnavigation tab (translated via I18n). controller: '/admin/products', # Controller that will be used. action: 'index' # Controller action that will be used. }, { name: 'modules.variants', # The name for the subnavigation tab (translated via I18n). controller: '/admin/variants', # Controller that will be used. action: 'index' # Controller action that will be used. } ] } }) # Register the module ability Alchemy.register_ability MyModuleAbility ~~~ ## Translate your module names All module names are passed through I18n within a `alchemy` namespace. The name will be used as translation key. You should namespace your module name (with i.e. `modules`), to prevent conflicts. ### Example Given a module name `modules.products` your translation has to be: ~~~ yaml # config/locales/de.yml de: alchemy: modules: products: Produkte ~~~ --- URL: "/v6/how_to_customize_tinymce.html" LLMS_URL: "/v6/how_to_customize_tinymce.md" --- # Customizing the Richtext Editor This guide explains how to override the default settings of the TinyMCE richtext editor and customize it. After reading this guide you will be able to: * [Overriding default settings](#override-the-default-settings) * [Customizing the toolbar](#custom-toolbar-buttons) * [Changing the default stylesheet](#setting-a-custom-stylesheet) ## Override the default settings To override the default settings you have to create a `tinymce.rb` initializer file into `config/initializers`. Alchemys default settings for TinyMCE can be found in the [documentation](https://www.rubydoc.info/github/AlchemyCMS/alchemy_cms/Alchemy/Tinymce) ### A Minimal Editor Let's reduce the options for formatting the text to a minimum. Because Alchemy merges the default options and your custom options you just need to pass the options you want to override to `Alchemy::Tinymce.init`. ~~~ ruby # config/initializers/tinymce.rb Alchemy::Tinymce.init = { toolbar: [ 'bold italic underline', 'pastetext charmap code | undo redo | alchemy_link unlink | alignleft aligncenter alignright alignjustify' ] } ~~~ Now only this three basic formatting options are available in the first toolbar to your editors. In the second toolbar are now the align formatting options available. ::: warning NOTE Overriding options always in the initializer applies to all richtext editors in all elements in Alchemy. ::: ## Custom toolbar buttons You can customize the TinyMCE toolbar with a lot of buttons. Find them in the [TinyMCE documentation](http://www.tinymce.com/wiki.php/Controls) ### Adding a format select box Let's add a format select to our editors so they can insert headlines into their texts. In the TinyMCE documentation we found our configuration option: ~~~ js tinymce.init({ ... block_formats: "Paragraph=p;Header 1=h1;Header 2=h2;Header 3=h3" }); ~~~ We just have to _convert_ this javacript object into a ruby hash. And we need to insert the format select into the set of buttons. ~~~ ruby # config/initializers/tinymce.rb Alchemy::Tinymce.init = { toolbar: [ 'bold italic underline | strikethrough subscript superscript | numlist bullist indent outdent | removeformat | fullscreen', 'pastetext | formatselect | charmap code | undo redo | alchemy_link unlink' ], block_formats: "Header 2=h2;Paragraph=p" } ~~~ That's it. Now the editor can format a block of text as headline 2. Nice! ::: warning NOTE Custom buttons always appear in all richtext editors in all elements in Alchemy. ::: ## Adding editor plugins It is possible to extend the tinymce editor with plugins. All you have to do is to download the tinymce plugin and copy the folder to `vendor/assets/javascripts/tinymce/plugins` and add the following content to `config/initializers/tinymce.rb` ~~~ ruby Alchemy::Tinymce.init = { plugins: Alchemy::Tinymce.plugins + ['colorpicker'], toolbar: [ 'bold italic underline | strikethrough subscript superscript | numlist bullist indent outdent | removeformat | alignleft aligncenter alignright | fullscreen', 'pastetext charmap hr | undo redo | alchemy_link unlink anchor | code | colorpicker' ] } ~~~ Another example configuration could be: ~~~ ruby Alchemy::Tinymce.plugins += ['textcolor'] Alchemy::Tinymce.init = { toolbar: [ 'bold italic underline | strikethrough subscript superscript | numlist bullist indent outdent | removeformat | fullscreen', 'pastetext | charmap code | undo redo | alchemy_link unlink | help | forecolor backcolor' ], } ~~~ This example adds the textcolor plugin and adds its button to the toolbar. ## Setting a custom stylesheet It is a good practice to set the stylesheet TinyMCE uses to display text in the editor area to the one you use to render the website. ::: tip INFO Please read more on custom stylesheets in the [TinyMCE documentation](http://www.tinymce.com/wiki.php/Configuration:content_css) ::: ### Create the css file Put a `tinymce_content.css` stylesheet into your `app/assets/stylesheets` folder. ### Setting the stylesheet ~~~ ruby # config/initializers/tinymce.rb Alchemy::Tinymce.init = { content_css: "/assets/tinymce_content.css" } ~~~ ### Compile the asset for production In production mode (on your server) Rails uses compiled assets to speed up the rendering of your page. As default Rails only compiles the `application.css` stylesheet. But you can tell Rails to compile our custom TinyMCE stylesheet as well. Open the `config/environments/production.rb` file in your editor and insert this line: ~~~ ruby config.assets.precompile += %w( tinymce_content.css ) ~~~ ## Per element customization You can also set TinyMCE configuration options per element. ~~~ yaml # elements.yml - name: minimal_text ingredients: - role: text type: Richtext settings: tinymce: toolbar: - 'bold italic underline', - 'pastetext charmap code | undo redo | alchemy_link unlink | alignleft aligncenter alignright alignjustify' ~~~ ## Configuration Syntax You can set any [TinyMCE configuration option](http://www.tinymce.com/wiki.php/Configuration). Just remember to _convert_ the javascript syntax into ruby. ### Convertions Tips Javascript and Ruby has very similiar syntax. Especially if you use the new Ruby 1.9 Hash syntax. So converting is no big deal. #### Object to Hash ~~~ js { "key" : "value" } => { key: "value" } ~~~ #### Array to Array Arrays have the same syntax. So you can just take them as they are. ~~~ js ["1", "2", "3", "4"] => ["1", "2", "3", "4"] ~~~ #### Object with Array as value ~~~ js { "key" : ["value1", "value2"] } => { key: ["value1", "value2"] } ~~~ #### Object with Object as value ~~~ js { "key" : { "value" : ["value1", "value2"] } } => { key: { value: ["value1", "value2"] } } ~~~ --- URL: "/v6/how_to_deploy_on_heroku.html" LLMS_URL: "/v6/how_to_deploy_on_heroku.md" --- # Heroku deployment [Heroku](https://heroku.com) is a cloud platform as a service. You can start with a free plan and run it for lifetime. In this guide you will learn how to deploy your existing Alchemy Site on Heroku. After finishing this guide you will know: * How to prepare your local Alchemy installation for Heroku * How to deploy code changes to Heroku ## Prerequisites We assume you already have a running Alchemy application on your local machine. In the following steps we call it `my-project`. Please replace it name with your actual name. All following shell commands need to be executed within your applications folder from the command shell. We also assume you already have an account on Heroku. [Otherwise signup now.](https://signup.heroku.com) You need the [Heroku Toolbelt](https://devcenter.heroku.com/articles/getting-started-with-ruby#set-up) for your OS installed to get access to the Heroku Command Line utility. Login with the heroku command from your command shell as described in the devcenter. The last thing we assume is you already have an Amazon AWS account. [Otherwise signup now.](https://aws.amazon.com) ## Amazon (AWS) setup ### Create S3 buckets Heroku does not provide data storage on its filesystem (beside a small amount for the application code and some temporary data). So we have to store our pictures and attachments outside of Heroku. Amazon S3 comes for the rescue. We need two S3 buckets, one for Alchemy pictures and another for Alchemy attachments. Please create two S3 buckets now and name them as follows: * `my-project-alchemy-pictures` * `my-project-alchemy-attachments` Please make sure to save the keys (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) Amazon gives you and keep them safe. ## Heroku setup ### Create a new application Create a [new Application from your Dashboard](https://dashboard.heroku.com/new). ### Install Memcache add-on We use Memcache to store and deliver cropped pictures and the page cache. This is important because otherwise page publishing and image cropping will not work accordingly. Lets add MemCachier to the application. ~~~ bash heroku addons:create memcachier:dev ~~~ This installs MemCachier with a 25M cache store for free. ### Set environment variables Run the following commands on your command shell to set some environment variables on your heroku account to provide the S3 key and bucket names. Make sure to replace `my-project` and `your-key` with the correct information. ~~~ bash heroku config:set AWS_REGION=bucket-region heroku config:set AWS_ACCESS_KEY_ID=your-key-id heroku config:set AWS_SECRET_ACCESS_KEY=your-key heroku config:set AWS_PICTURES_BUCKET=my-project-alchemy-pictures heroku config:set AWS_ATTACHMENTS_BUCKET=my-project-alchemy-attachments ~~~ ## Code adjustments ### Add gems We need to add a few gems to the `Gemfile` of the Application. ~~~ ruby gem 'thin' group :production do gem 'pg' gem 'rails_12factor' gem 'rack-cache', require: 'rack/cache' gem "dragonfly-s3_data_store" gem 'dalli' gem 'kgio' end ~~~ Now bundle the gems ~~~ bash bundle install ~~~ ::: tip Heroku uses PostgreSQL. If you want to use a different database for your development environment you need to define `group: "development"` for the relevant database adapter gem. ::: ### Use thin webserver instead of webrick Webrick is not the best choice for running rails applications in production. Lets tell Heroku we want to use thin instead. Create a new file in your applications root directory named `Procfile` and paste the following line. ~~~ ruby web: bundle exec thin start -p $PORT -e $RACK_ENV ~~~ ### Create initializer Create a new initializer `config/initializers/alchemy_file_storage.rb` in your application code to overwrite the information how and where Alchemy pictures and attachments are stored. You can just paste the following code and adjust the region if necessary. Be aware to use the same region as you did when creating the buckets. We set up the application to use Amazon S3 storage on production while using the filesystem in other environments. ~~~ ruby if Rails.env.production? require 'dragonfly' require 'dragonfly/s3_data_store' aws_defaults = { access_key_id: Rails.configuration.aws_access_key_id, secret_access_key: Rails.configuration.aws_secret_access_key, region: Rails.configuration.aws_region, storage_headers: {'x-amz-acl' => 'public-read'}, url_scheme: 'https' } Dragonfly.app(:alchemy_pictures).configure do plugin :imagemagick datastore :s3, { bucket_name: Rails.configuration.aws_pictures_bucket }.merge(aws_defaults) end Dragonfly.app(:alchemy_attachments).configure do datastore :s3, { bucket_name: Rails.configuration.aws_attachments_bucket }.merge(aws_defaults) end end ~~~ ### Configure rack-cache for production environment ~~~ ruby # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like # NGINX, varnish or squid. config.action_dispatch.rack_cache = true # use the Dalli client for its cache-store config.cache_store = :dalli_store client = Dalli::Client.new((ENV["MEMCACHIER_SERVERS"] || "").split(","), :username => ENV["MEMCACHIER_USERNAME"], :password => ENV["MEMCACHIER_PASSWORD"], :failover => true, :socket_timeout => 1.5, :socket_failure_delay => 0.2, :value_max_bytes => 10485760) config.action_dispatch.rack_cache = { :metastore => client, :entitystore => client } config.static_cache_control = "public, max-age=311040000" ~~~ ### Make S3 informations accessible to Rails configuration Paste the following lines to your `config/environments/production.rb` inside the configure block. The Rails application gets access to the S3 information provided via environment variables. ~~~ ruby config.aws_region = ENV['AWS_REGION'] config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID'] config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] config.aws_pictures_bucket = ENV['AWS_PICTURES_BUCKET'] config.aws_attachments_bucket = ENV['AWS_ATTACHMENTS_BUCKET'] ~~~ ### Prepare your seeds.rb In order to populate the remote database with basic information (which is needed for running Alchemy) please paste the following to your `db/seeds.rb`. You can skip this step if you already have it. ~~~ ruby Alchemy::Seeder.seed! ~~~ ## Git repository ### Create a new repo If your application code is already checked-in to a git repository you can skip this. Otherwise initialize a new git repository now. ~~~ bash git init ~~~ ### Make sure you do not store certain files in your git repo We do not want certain files in git. Please make sure you ignore at least following files/folders in your `.gitignore` file. ~~~ bash /tmp/* /log/* /uploads /public/assets /public/pictures /config/database.yml .DS_Store /db/*.sqlite3 ~~~ ### Commit your files to the git repository ~~~ bash git add . git commit -am "Initial commit" ~~~ ::: tip Never store any critical informations like database or ssh passwords in your git repository, even if you have a private one. ::: ### Add heroku as a git remote You need to add heroku as a remote git repository. Each time you push the master branch to heroku the application will be deployed. ~~~ bash heroku git:remote -a my-project ~~~ ## Deployment ### First deployment Lets deploy the application to heroku the first time! Its easy as pushing our repository to the heroku git remote. ~~~ bash git push heroku master ~~~ ### Migrate and seed the database We need to migrate the database and populate it with Alchemy seed data. ~~~ bash heroku run rake db:migrate heroku run rake db:seed ~~~ ### Verify the deployment Open a browser and enter the domain Heroku gave you. Usually its a subdomain consisting of your project name and the domain herokuapp.com (e.g.: https://my-project.herokuapp.com) ::: tip You can always get your domain from the settings on Heroku. E.g.: https://dashboard.heroku.com/apps/my-project/settings (replace my-project with the correct name) ::: ### Deploy the app later on Each time you want to deploy to Heroku just push your git commits to the heroku remote: ~~~ bash git push heroku master ~~~ --- URL: "/v6/how_to_extend_alchemy.html" LLMS_URL: "/v6/how_to_extend_alchemy.md" --- # Extending Alchemy At some point you may come across the need of adding some behavior to already existing controllers or models, that Alchemy provides. Obviously you could just copy the files, insert them into your projects and edit them there, but this isn't a really clean solution. * This section will give you a hint on how to extend existing Alchemy files without overriding everything. ## Tell Rails to load our extensions Tell Rails to load our extensions by adding some lines into `config/application.rb` ~~~ ruby # config/application.rb config.to_prepare do Dir.glob(Rails.root.join('app', '**', '*_extension.rb')) do |f| Rails.configuration.cache_classes ? require(f) : load(f) end end ~~~ ## Adding an extension After you set up the loading for our extensions, we can actually start making some. Lets assume you want to add a before_action method to the `Alchemy::PagesController`. You go into your host app and add a file in `app/controllers/alchemy/` called `pages_controller_extension.rb`. It's important to add the `_extension` to the filename so Rails will load them. Into this file you add the following code: ~~~ ruby module AlchemyPagesControllerExtension end Alchemy::PagesController.prepend AlchemyPagesControllerExtension ~~~ Into this block you can add anything you want your controller to do. As already mentioned, we want to add a before_action to this controller for some show off. It could then look like this: ~~~ ruby module AlchemyPagesControllerExtension def prepended(base) base.before_action :some_method, only: :show end end ~~~ The last step is adding some_method to your `ApplicationController`. This is done like you are used to in Rails. --- URL: "/v6/ingredients.html" LLMS_URL: "/v6/ingredients.md" prev: "elements" next: "render_images" --- # Ingredients ## Overview Ingredients store the actual content of your site. They are stored as instances of `Alchemy::Ingredient` subclasses on each `Alchemy::Element` instance. Alchemy uses Rails [Single Table Inheritance](https://guides.rubyonrails.org/association_basics.html#single-table-inheritance-sti) to store the values in the same table. Alchemy comes with a lot of predefined ingredients for the regular needs of a website project. Combine them like a chemestry kit into [elements](elements). Ingredients are Rails models. It is pretty easy to [add your own ingredient class](how_to_create_custom_ingredients) as well. ## Definition Ingredients are defined as `ingredients` on an [element definition](elements#defining-elements). ### Global ingredient settings When defining ingredients, you need to provide a `role` and `type`. You can set hints and default values as well. ~~~ yaml # config/alchemy/elements.yml - name: article ingredients: - role: headline type: Text hint: This is the headline - role: color type: Text settings: input_type: color - role: copy type: Richtext default: Lorem ipsum dolor as_element_title: true ~~~ * **role** `String` _required_ A lowercased **unique** (per element) role of the ingredient. Separate words needs to be underscored. * **type** `String` _required_ An type this ingredient is of. Alchemy has lots of built in ingredients for [simple text](#text), [rich text](#richtext), [images](#picture), [booleans](#boolean) and more. * **hint** `String|Symbol|Boolean` A hint for the user in the admin frontend that describes what the ingredient is used for. The hint is translatable if you provide an I18n translation `Symbol` instead of a `String`. You may also set it to `true` to default to the I18n key `alchemy.ingredients_hints.your_ingredient_role`. * **default** `String` The default text to prefill newly created elements. You may also use a symbol to set it to the I18n key `alchemy.default_ingredients_texts.your_ingredient_role` * **as_element_title** `Boolean` For the displayed element title, the first ingredient is used. Use this setting to override this behaviour and show other ingredient as element title. * **settings** `Hash` A set of options to configure the ingredient. Each ingredient has its own set of options listed below. ### Individual ingredient settings Each ingredient type can have its own type of settings. To configure these settings you have to pass them into its `settings` key in the `elements.yml`. ~~~ yaml {5-6} - name: my_element ingredients: - role: headline type: Text settings: linkable: true ~~~ ## Text Stores plain text of 255 chars max. Use this for a headline, or a product name. The editor is renderd as a single lined input field. The view output will be sanitized and HTML escaped. ### Settings * **linkable** `Boolean` If set to `true`, the user can add a link to the text. * **input_type** `String` Change the [input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types) of the form field displayed in the editor. ### Example ~~~ yaml - name: button type: Text settings: linkable: true ~~~ ## Richtext Used to store paragraphs of formatted text. The editor is rendered as a textarea with embedded Tinymce Editor. ### Settings You can customize the Tinymce editor of a single element instance. * **tinymce** `Hash` ### Example ~~~ yaml - name: text type: Richtext settings: tinymce: style_formats: - title: 'Subheadline' block: 'h3' ~~~ ::: tip INFO See the [Tinymce customization guide](how_to_customize_tinymce) for all available options ::: ## Picture Store references to pictures the editor assigns from the library. The editor partial is rendered as a picture editor with a lot of options (i.e. image cropper). The view partial renders the assigned picture, resizes it and crops it if needed. ::: tip INFO See the [rendering images](render_images) guide for further information on the powerful image rendering engine of Alchemy. ::: ### Settings * **size** `String` The size the image should be downsized to. I.e. 400x300 * **crop** `Boolean` (default: false) Crop the image to given size. This also enables the build in cropper tool. * **srcset** `Array<String>` A list of sizes of this image uses as sources list. Best used with the `sizes` setting for implementing responsive images. * **sizes** `Array<String>` A list of screen sizes the image sources should be used for. Best used with the `srcset` setting for implementing responsive images. ## Date Use this to store a `DateTime` value. Renders a datepicker in the editor partial. The view output is passed through Rails' I18n Library, so it is fully localizable. ### Settings * **date_format** `String|Symbol` Either a `String` with the format of [Rubys `strftime`](https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/Date.html#method-i-strftime) or a `Symbol` of a [date/time format](https://guides.rubyonrails.org/i18n.html#adding-date-time-formats) defined in your locale files. ## Html Useful to store HTML code (i.e. a embed, or tracking code). ::: warning The view renders the raw, not sanitized or escaped output. **So be careful!** ::: ## Boolean Stores a `Boolean` value in the database. Renders a checkbox in the editor partial. ## Select Renders a select box in the editor partial and stores the value as `String`. Useful for letting your user select from a limited set of choices. ### Settings * **select_values** `Array` A list of values your users can select from. Use [a two dimensional array](https://guides.rubyonrails.org/form_helpers.html#the-select-and-option-tags) for having value and text pairs. ### Example ~~~ yaml - name: width type: Select settings: select_values: ['200', '300', '400'] ~~~ ::: tip If you need dynamic values (ie, a from a product catalogue), please [create a custom ingredient class](how_to_create_custom_ingredients) that provides the values. ::: ## Link Stores a url in the database. Useful for linking things, where the editor should not set the linked text itself. ::: tip If you want the linked text to be editable by the editor use the [**Text**](#text) with `linkable: true` option instead. ::: ## Page References an [Alchemy::Page](pages). Renders a select box in the editor partial. Useful for contact form follow up pages or other use cases where you want to reference another page. ### Example ~~~ yaml - name: contact_form ingredients: - role: follow_up_page type: Page ~~~ ## Rendering ingredients within your element Similar to [rendering elements within layouts](elements.html#render_elements_in_your_layout), ingredients are rendered on an element. Page layouts use the `<%= render_elements %>` helper to load an elements partial, elements use the `<%= element_view_for %>` helper to expose a block that makes it easy to access the ingredients (instances of `Alchemy::Ingredient` subclasses) of the element. For example. With an `article` element like this: ~~~ yaml # config/alchemy/elements.yml - name: article ingredients: - role: headline type: Text - role: color type: Text settings: input_type: color - role: copy type: Richtext ~~~ The `_article.html.erb` template generated by `rails alchemy:elements --skip` would look like this: ~~~erb <%- cache(article) do -%> <%= element_view_for(article) do |el| -%> <div class="headline"> <%= el.render :headline %> </div> <div class="color"> <%= el.render :color %> </div> <div class="copy"> <%= el.render :copy %> </div> <%- end -%> <%- end -%> ~~~ Note how `element_view_for` allows you to call `el.render` on ingredients within the block. ::: tip `element_view_for` wraps the block in a `<div>` with a set ID and class by default, but this is [customizeable](https://github.com/AlchemyCMS/alchemy_cms/blob/main/app/helpers/alchemy/elements_block_helper.rb#L137) if you pass in arguments: `<%= element_view_for(article, tag: :span, id: 'custom_id', class: 'custom_class') do |el| -%>` ::: Without using the `element_view_for` helper, you can still access ingredients: `article.ingredient_by_name('headline')&.value` But the `el.render` helper takes care of generating the appropriate DOM elements to display the ingredient based on its type. It is recommended you rely on these helpers unless you are comfortable with the structure of the ingredient model you are trying to render. Just like `element_view_for`, you can pass `options` and `html_options` to `el.render`: ~~~erb <%= el.render :headline, options = {}, html_options = {} %> ~~~ You can check out the [`ElementViewHelper#render` helper](https://github.com/AlchemyCMS/alchemy_cms/blob/b8cc62493693a070f7457081760708bf28c13e34/app/helpers/alchemy/elements_block_helper.rb#L31) for details. ::: warning NOTE Options are not universally applied by all element types. For example, `ElementRichText` ignores `html_options` since it's already in HTML format and isn't wrapped in any special div. And you can't prevent the `ElementPicture` from generating an `<img>` tag. ::: ### Options The `options = {}` hash is used for formatting the child elements' tags. You can pass `false` to not include tags inside the wrapper element. For more, see the source code [here](https://github.com/AlchemyCMS/alchemy_cms/blob/56e95a07446fb6832676acfc1e8c7047d1b80495/app/helpers/alchemy/elements_block_helper.rb#L128) and [here](https://github.com/AlchemyCMS/alchemy_cms/blob/main/app/helpers/alchemy/elements_helper.rb#L193) ### Html_options The `html_option = {}` are: * `:tag` - The HTML tag to be used for the wrapping element * `:id` - The wrapper tag's DOM ID. * `:class` - The wrapper tag's DOM class. --- URL: "/v6/pages.html" LLMS_URL: "/v6/pages.md" prev: "about" next: "elements" --- # Pages ## Overview In contrary to most CMSs pages in Alchemy **do not hold the actual content**. The actual content is stored in [ingredients](ingredients) inside of [elements](elements) pages are built of. Pages have a unique friendly url and are organized as nested tree and represent the structure of your website. Beside a language, a page has attributes for name, title, visibility, published and restriction status and all SEO relevant attributes like meta tags and meta descriptions. Every page has a [definition](#defining-page-layouts) which defines additional properties like caching, uniqueness and it defines [elements](elements) that can be placed on that Page. ## Defining page types Page types are defined in the `config/alchemy/page_layouts.yml` file. Every page type needs at least a name. You don't need to set every option. It depends on what you need for pages with that type. ### Recommended settings * **name** `String` *required* The name of the page type used for views and inside the database. You can render a page by type with the `render_page_layout(name)` helper. * **elements** `Array` A list of element names that can be placed on this type of page i.e. `[text, picture]`. [Elements](elements) are defined inside the `elements.yml` file. * **autogenerate** `Array` A list of element names that are autogenerated after creating a Page of that type. * **unique** `Boolean` (Default `false`) Pass `true` and the user can only choose this page type once inside a language tree. * **cache** `Boolean` (Default `true`) Pass `false` to disable the cache headers for this kind of pages. **Recommended for contact forms** and such likes. * **layoutpage** `Boolean` (Default `false`) Layout pages (or [global pages](#global-pages)) are outside the normal page tree and can be used to place "global" Elements like a header and footer. * **taggable** `Boolean` (Default `false`) Pass `true` to be able to assign tags within page settings. ### Optional settings * **hide** `Boolean` (Default `false`) Pass `true` to hide this page type from the user. * **searchresults** `Boolean` (Default `false`) Pass `true` to use this type of page for rendering the search results of the build in fulltext search. * **feed** `Boolean` (Default `false`) Pass `true` to enable a RSS feed of news elements from this page. * **controller** `String` Controller to use instead of the default `Alchemy::PagesController` * **action** `String` Controllers action to use instead of the default `Alchemy::PagesController#show` ### Example Lets say you want to create a contact page with a headline element, a contactform and a text element on it. This page should be unique, because you don't want to give your content manager the possibility to create more than one contact form. This page must not be cached, because of validation messages and user specific form content. We also want to autogenerate the headline and the contactform element after the page gets created. ~~~ yaml # config/alchemy/page_layouts.yml - name: contact cache: false unique: true elements: [headline, contactform, text] autogenerate: [headline, contactform] ~~~ ::: warning NOTE Please ensure to restart the Rails server after editing `page_layouts.yml`. ::: ## Page templates Each page type (called `page_layout` in Alchemy) has a Rails view partial which is yielded on the Rails application layout (`app/views/layouts/application.html.erb`). All page type partials live in the `app/views/alchemy/page_layouts` folder. They are named after the pages `page_layout` you defined in the `page_layouts.yml` file. ::: warning NOTE If no page type partial is found for a page, the `_standard.html.erb` partial gets rendered instead. ::: ### Template generator There is no need to create these partials manually. AlchemyCMS comes with a Rails generator task which creates these partials for you. So after defining the page layouts, you can generate all the corresponding partials for them. ~~~ bash bin/rails g alchemy:page_layouts --skip ~~~ Using the example above, which defines a contact page type, the generator will create a partial named `_contact.html.erb`. ::: tip You can pass `--template-engine` or `-e` as option to use one of `haml`, `slim` and `erb`. The default depends on your default template engine in your Rails host app. ::: ### Rendering elements Alchemy does not place any HTML markup in your generated page layouts partial. So: ~~~ erb <%= render_elements %> ~~~ is all you will see. Feel free to customize the HTML so it fits your needs. ::: tip The `render_elements` view helper has lots of options. Please have a look at the [`Alchemy::ElementsHelper` documentation](https://www.rubydoc.info/github/AlchemyCMS/alchemy_cms/Alchemy/ElementsHelper#render_elements-instance_method) ::: ## Global pages Global pages (or layout pages) are pages that are not in the default page tree (your navigation). They will never get rendered on its own. Use them to store shared elements that should be rendered on multiple other pages (ie. footer, header, tracking codes etc) or somewhere directly on the application layout. To define a global page set `layoutpage: true` in the page type definition of that page. ### Render an element from a global page To render an element from a global page use the `from_page` option of the `render_elements` helper. #### Example ~~~ erb <%= render_elements only: 'news_teaser', from_page: Alchemy::Page.find_by(name: 'sidebar') %> ~~~ ::: tip You can pass a `page_layout` name as a `String`, an `Array` of page type names, or an instance of a certain `Alchemy::Page`. ::: ## Caching If using the global caching option (defined in `config/alchemy/config.yml`) - which is enabled by default - your page requests will deliver cache headers. Most browsers, CDNs and proxys use these headers to cache the page. ::: tip You can disable cache headers for certain pages by using the `cache: false` setting. ::: ### Russian doll caching You can use Rails' "Russian-Doll-Caching" to cache page templates. ```erb <% cache @page do %> <%= render_elements %> <% end %> ``` ::: warning Be sure to not cache page templates that have elements with forms on it, like contact or comment forms. Rails' csrf protection token is placed inside the `<form`> tag and caching it will break form submissions. ::: ## Translate page type names Page type names are passed through the `I18n` library. You can translate them in your Alchemy locale files. ### Example ~~~ yaml # config/locales/alchemy.de.yml de: alchemy: page_layout_names: contact: Kontakt search: Suche ~~~ ::: tip All translation keys used by Alchemy are scoped under the `alchemy` namespace. ::: --- URL: "/v6/render_images.html" LLMS_URL: "/v6/render_images.md" prev: "essences" next: "configuration" --- # Render Images Alchemy uses [Dragonfly](https://markevans.github.io/dragonfly/) to render images. Images are stored as master images in the pictury library. The editor just assigns these master images to content elements you provide. You also set the image rendering bounderies. The max width and height values, or even the size an image should be cropped to. Alchemy even comes with a built in image cropping tool, so that your editors can define the mask to be used. ## Configuration * **output_image_jpg_quality** `Integer` (Default `85`) If image gets rendered as JPG this is the quality setting for it. * **preprocess_image_resize** `String` (Default `nil`) Example `1000x1000`. If you are limited on diskspace use this option to downsize large images after upload. * **image_output_format** `String` (Default `jpg`) The global image output format setting. ::: tip You can always override the output format while rendering your essence with passing `{format: 'png'}` to the `el.render(:image)` method. ::: ### The default configuration ~~~ yaml output_image_jpg_quality: 85 preprocess_image_resize: image_output_format: jpg ~~~ ## Rendering In most cases the `el.render(:image)` method is the perfect fit for rendering images. It handles all advanced stuff for you (Loading the image instance, setting all needed options and sets the correct security token). ### Rendering Options * **size** `String` The dimensions the image should be resized to, while keeping the aspect ratio. Example `"400x300"`. * **crop** `Boolean` Pass `true` to crop the image to the given size. * **format** `Symbol|String` The output format of the image. `:jpg`, `:gif` or `:png`. * **quality** `Integer` The quality of the rendered jpg image. Obviously only used on jpg images. ### Advanced rendering If you want to render an image on your own, please have a look at the `show_alchemy_picture_url` helper in the [Alchemy documentation](https://www.rubydoc.info/github/AlchemyCMS/alchemy_cms/Alchemy/UrlHelper#show_alchemy_picture_path-instance_method). ## Caching In production environments you want to implement a cache to avoid rendering the same image over and over again. All supported rendering options are reflected in the url schema. Even the quality of a rendered jpg image and all cropping options. So adding a CDN or HTTP cache (ie. `Rack::Cache`) is the recommended caching option. ::: tip Please be sure to read the [Dragonfly caching guide](https://markevans.github.io/dragonfly/cache) for further informations. ::: --- URL: "/v6/upgrading.html" LLMS_URL: "/v6/upgrading.md" prev: "configuration" next: false --- # Updating AlchemyCMS This guide describes how to update AlchemyCMS to a new version. Updating Alchemy is mostly three steps. 1. [Update the gem](#update-the-gem) 2. [Run the upgrade task](#run-the-upgrade-task) 3. [Follow ups](#follow-ups) ## Update the gem If you use Alchemy from a git branch, point it to the next stable release. ~~~diff ... -gem 'alchemy_cms', github: 'AlchemyCMS/alchemy_cms', branch: '5.2-stable' +gem 'alchemy_cms', github: 'AlchemyCMS/alchemy_cms', branch: '5.3-stable' ... ~~~ If you use Alchemy from rubygems, point it to the next stable release. ~~~diff ... -gem 'alchemy_cms', '~> 5.2' +gem 'alchemy_cms', '~> 5.3' ... ~~~ If you are using `alchemy-devise` for authentication, then update the gem as well. The major and minor versions of the `alchemy_cms` and the `alchemy-devise` gems match. ~~~diff ... -gem 'alchemy_cms', '~> 5.2' +gem 'alchemy_cms', '~> 5.3' -gem 'alchemy-devise', '~> 5.2' +gem 'alchemy-devise', '~> 5.3' ... ~~~ Now update via bundler ~~~bash bundle update alchemy_cms alchemy-devise ~~~ ## Run the upgrade task Now you can run the upgrade task. While upgrading, you will get informations about the process on your screen. ~~~bash bin/rake alchemy:upgrade ~~~ and follow the on screen instructions. ### Update the default config If new configuration options have been introduced you see them in the `config/alchemy/config.yml.defaults` file. Simply copy them over and have a look at the `git diff`. Keep your changes remove or add new keys as necessary. If you have also `alchemy-devise` installed you need to . ~~~bash bin/rails g alchemy_devise:install ~~~ ### Run single upgrade tasks Upgrade tasks can also be run on its own. For example to run only the `alchemy:upgrade:config` task: ~~~bash bin/rake alchemy:upgrade:config ~~~ A list of all upgrade tasks can be listed with ~~~bash bin/rake -T alchemy:upgrade ~~~ ## Follow ups Most of the time the upgrader does all the work for you. But sometimes the upgrade needs some manual work. This will be noted as TODOs at the end of the upgrade task. Please follow them carefully. ## Major version upgrades Major version upgrades follow the same upgrade process as minor versions, but they will have breaking changes. Major versions remove deprecated features and old upgrade tasks. Please make sure you addressed all deprecations and run all upgrades in the current version before upgrading to the next major version. ## Finally Please always verify the upgrade by looking through the `git diff` and by running your test suite for customization you made. Also the [CHANGELOG](https://github.com/AlchemyCMS/alchemy_cms/blob/main/CHANGELOG.md) is a good place to look for changes. --- URL: "/v7" LLMS_URL: "/v7.md" layout: "home" hero: name: "AlchemyCMS" image: src: "/icon.svg" alt: "AlchemyCMS Icon" text: "The open source content framework built in Rails" actions: - text: "Get Started →" link: "getting_started" theme: "brand" features: - title: "Not a common CMS" icon: "🚀" details: "Alchemy is a CMS framework. It enables you to build your own CMS." - title: "Highly customizable" icon: "🛠" details: "Build even complex websites in a shorter time frame." - title: "Easy to use" icon: "😇" details: "The user interface is powerful, easy to use and intuitive to learn." --- <h2 style="text-align: center">Sponsored by</h2> <div style="text-align: center; margin: 2rem 0; display: flex; justify-content: center; align-items: center"> <a href="https://blish.cloud" target="_blank"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 117.1 25.5167" width="117.1" height="auto" class="blish-logo" > <path fill="currentColor" d="m117.324.644h-5.457v24.7h5.457zm-16.272 15.011h6.373v-4.679h-6.373v-10.332h-5.352v24.7h5.348zm-27.852-8.49c0-1.172 1.241-2.019 3.26-2.019a5.191 5.191 0 0 1 4.462 2.128l3.654-2.906a9.658 9.658 0 0 0 -8.256-4.108c-4.541 0-8.51 2.551-8.51 6.983 0 3.969 2.443 5.88 5.989 7.161 3.437 1.281 6.201 1.813 6.201 3.97 0 1.172-.955 2.443-3.684 2.443a6.961 6.961 0 0 1 -6.205-3.26l-3.969 2.728c1.527 2.975 4.817 5.457 10.135 5.457 6.274 0 9.18-3.191 9.18-7.545 0-3.546-1.842-5.634-6.3-7.338-4.009-1.577-5.95-1.646-5.957-3.694m-22.1 18.182h5.457v-24.703h-5.457zm-24.5 0h15.1v-4.964h-9.635v-19.739h-5.457zm-10.433-7.308a5.805 5.805 0 0 0 -4.967-6.127 4.969 4.969 0 0 0 3.152-4.964c0-3.654-3.122-6.3-7.052-6.3h-7.05v4.675h6.215a2.512 2.512 0 0 1 2.62 2.4v.138a2.534 2.534 0 0 1 -2.453 2.62h-6.382v4.219h7.279a2.959 2.959 0 0 1 3.211 2.689 1.6 1.6 0 0 1 .01.217 3.027 3.027 0 0 1 -3.044 3.014h-4.8v.02h-2.656v4.7h8.412a7.211 7.211 0 0 0 7.506-6.9 2.63 2.63 0 0 0 0-.394" /> </svg> </a> </div> --- URL: "/v7/about.html" LLMS_URL: "/v7/about.md" prev: "getting_started" next: "pages" --- # About AlchemyCMS Alchemy is a user friendly and flexible Rails based CMS. In this guide you will learn about Alchemy's powerful features: * The flexible content storing architecture * The image processing magick * Caching * The resources controller ## Rails Engine AlchemyCMS is a Ruby on Rails engine. Read about [engines in the Rails guides](https://guides.rubyonrails.org/engines.html). ## The Core modules AlchemyCMS comes with six core modules: 1. [Pages](pages) 2. Languages 3. Sites 4. Users 5. Tags 6. Library (Pictures & Attachments) The core modules can be extended by own modules. A module should be a Rails engine that registers itself at Alchemy. Read more about [creating own modules](how_to_create_modules). ## Storing architecture Unlike many other CMS's that store a whole page body with complete HTML markup, Alchemy only stores unformatted text, ids of objects (like attachments and pictures) and only some richtext content in the database. No HTML markup, no CSS, no styling, no layout. Just pure content. You, the webdeveloper are in full control of the markup and styling. The editor just manages the content. ### The render flow Alchemy uses [Rails' partials](https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials). It has no own templating language and no *special files*. When Alchemy renders a typically Page, these files get usually rendered: 1. `app/views/layouts/application.html.erb` 2. `app/views/alchemy/page_layouts/_<page_layout>.html.erb` 3. `app/views/alchemy/elements/_<element>.html.erb` 4. `app/views/alchemy/ingredients/_<ingredient>_view.html.erb` ::: tip Alchemy comes with useful helpers that help render these partials. For further information please [have a look into the Alchemy::PagesHelper documentation](https://www.rubydoc.info/github/AlchemyCMS/alchemy_cms/Alchemy/PagesHelper.html) ::: ### Tips for page and content structure When working with AlchemyCMS the very first thing you should do is identify the website´s layout different page types (called page layouts). Every page which is structurally different to another, should have its own page layout. A page layout is a HTML template with specified properties. [More about pages and their layouts »](pages.html#defining-page-layouts) After that you should identify fixed areas on the layout and define them for sidebars, heroes and other groups of content. Fixed elements render on page layouts and are acting as containers for element groups. [More about fixed elements »](elements#render-a-group-of-elements-on-a-fixed-place-on-the-page) In any case you should split the layout into elements. That means grouping small parts of the websites content into reusable containers. Elements are rendered on page layouts and are the key component of Alchemy. [More about elements »](elements) An ingredient is the smallest part of content in Alchemy and represents a headline, an image, paragraphs and other values of content. [More about ingredients »](ingredients) ## Processing images Alchemy uses [ImageMagick®](https://www.imagemagick.org) and [Dragonfly](https://markevans.github.io/dragonfly/) to render images on-the-fly. Images are stored as master images in the picture library. The editor just assigns these images to elements you provide them. You also set the image rendering boundaries. The max width and heigth values, or even the size an image should be cropped to. Alchemy comes with a built-in image cropping tool, so that the editor can define the mask to be used. [Read more about how to use the powerful image processing tools](render_images). ## Caching If not deactivated in the [configuration](configuration), Alchemy uses Rails "Russian Doll Caching" for fast content delivering. This will cache all [page layout](pages) partials where [elements](elements) are rendered on. With every page request Alchemy sends cache headers that are used by CDNs, proxies and your vistors browser to cache the page body. ## Rails admin for your custom models Alchemy can also be your Rails admin for your own custom models and controllers. When developing own modules for Alchemy, your controller can inherit from that resources controller (`Alchemy::Admin::ResourcesController`) to get typical Rails CRUD methods. The standard templates for the backend views are also rendered. They can be overwritten in your app, just use the expected filenames and variables. Read more details about the resources controller in the [module development](how_to_create_modules) guide. --- URL: "/v7/configuration.html" LLMS_URL: "/v7/configuration.md" prev: "render_images" next: "upgrading" --- # Configuration of AlchemyCMS Alchemy configuration is done in the `config/alchemy/config.yml` file. Use ~~~ bash bin/rake alchemy:install ~~~ or ~~~ bash bin/rails g alchemy:install ~~~ to generate it. ## The configuration file Please [have a look into the source code](https://github.com/AlchemyCMS/alchemy_cms/blob/master/config/alchemy/config.yml). The file is pretty well documentated and much more up to date then this guide. ## Environment related configurations Beside using that one configuration file you also can seperate certain config settings for specific environments. For example: You want to enforce SSL in production, while in your development environment you don't. Its easy to achieve by setting up a second configuration file for development which contains nothing but the ssl enforcing setting. Your apps default config: ~~~ your_app/config/alchemy/config.yml ~~~ Environment specific config: ~~~ your_app/config/alchemy/development.config.yml ~~~ ::: tip NOTE When running your application in development mode both config files get merged, while the environment specific config takes precedence over the default config. ::: --- URL: "/v7/elements.html" LLMS_URL: "/v7/elements.md" prev: "pages" next: "ingredients" --- # Elements ## Overview Elements are reusable blocks of content that belong together, visually and contextually. Imagine you combine a headline, a text block and a picture - this is a typical element, maybe you want to call it an "article". Some people speak of widgets, blocks or components. All these are elements in Alchemy. Elements can be nested into each other and are the key of Alchemy's flexible content architecture. They give you a powerful tool to build the CMS you need. The ingredients of an element are of a specific [type](ingredients). Each ingredient represents a data type that store values in the database. ## Templates Each element has a template (they are called [partials in Rails](https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials)). They live in `app/views/alchemy/elements/`. ::: tip NOTE You don't need to create the files yourself. Use [the built in generator](#generating-the-partials) to let Alchemy generate them for you. ::: ## Defining elements Elements are defined in the `config/alchemy/elements.yml` file. Element definitions are written in [YAML](http://yaml.org) If this file does not exist yet, use the scaffold generator to do that now: ~~~ bash bin/rails g alchemy:install ~~~ The generator also creates all the other basic folders and files for setting up your website with Alchemy. ::: warning NOTE The element definitions are cached. Please restart the server after editing the `elements.yml`. ::: ### Example element definition ~~~ yaml # config/alchemy/elements.yml - name: article unique: true ingredients: - role: image type: Picture - role: headline type: Text as_element_title: true - role: copy type: Richtext ~~~ The element in this example is named **"article"** and can be placed only once per page (because it is `unique`). It has three ingredients of the following types: * [Picture](ingredients.html#picture) * [Text](ingredients.html#text) * [Richtext](ingredients.html#richtext) ::: tip You can select which ingredient will be used for the preview text in the element's title bar in the admin frontend by adding `as_element_title: true` to the desired ingredient. In the example above, the **"headline"** ingredient would be used. ::: ### Element settings The following settings can be used to define elements in the `elements.yml`. * **name** `String` _required_ A lowercased **unique** name of the element. Separate words need to be underscored. The name is used in the `page_layouts.yml` file to define on which pages the element can be used. It is also part of the `app/views/alchemy/elements` view partials file names. The name is [translatable](#translating-elements) for the user in the admin frontend. * **unique** `Boolean` (Default: `false`) Passing `true` means this element can be placed only once on a page. * **hint** `String` A hint for the user in the admin interface that should be used to describe what the element is used for. The hint is [translatable](#translating-elements) if you provide an I18n translation key instead of a complete sentence. The hint itself will be displayed as a small question mark icon and will reveal a tooltip once hovered by the user. * **message** `String` A prominent informational message displayed at the top of the element editor form in the admin interface that can be used to give your user additional information. You can even use simple html to add some emphasis to your message. * **warning** `String` A prominent warning message displayed at the top of the element editor form in the admin interface that can be used to warn your user about something. You can use simple html to add even more emphasis to your warning. * **nestable_elements** `Array` A collection of element names that can be nested into the element. * **taggable** `Boolean` (Default: `false`) Enables the element to be taggable by the user in the admin frontend. * **fixed** `Boolean` (Default: `false`) Used to separate an element from the normal flow. When `true`, the element is rendered on the page only with the explicit call of the `fixed_elements` scope. See [fixed elements](elements.html#render-a-group-of-elements-on-a-fixed-place-on-the-page) for more details. * **ingredients** `Array` A collection of ingredients the element contains. A ingredient has to have a `role` (unique per element) and a `type`. ::: tip Have a look at the [ingredients guide](ingredients) to get more informations about available ingredient types. ::: In the following examples you will see how to use these settings. ## Nestable elements You are able to nest elements into other elements. Just define nestable elements in your `elements.yml` file. ### Example ~~~ yaml - name: article ingredients: - role: headline type: Text nestable_elements: - text - picture - name: text ingredients: - role: text type: Richtext - name: picture ingredients: - role: text type: Picture ~~~ ::: warning NOTE Nested elements can also have `nestable_elements`. Just don't get too crazy with it, though. ::: ## Rendering nested elements Use the `render` helper to render all nested elements as a collection. ~~~ erb <%= element_view_for(article) do |el| %> <h3><%= el.render :headline %></h3> <div class="text-blocks"> <%= render article.nested_elements %> </div> <% end %> ~~~ Or `render` a group of nested elements by filtering the `nested_elements` collection by element name. ~~~ erb <%= element_view_for(article) do |el| %> <h3><%= el.render :headline %></h3> <div class="text-blocks"> <%= render article.nested_elements.named(:text) %> </div> <div class="pictures"> <%= render article.nested_elements.named(:picture) %> </div> <% end %> ~~~ Or `render` a single nested element by loading it from the `nested_elements` collection by element name. ~~~ erb <%= element_view_for(article) do |el| %> <h3><%= el.render :headline %></h3> <div class="picture"> <%= render article.nested_elements.find_by(name: :picture) %> </div> <div class="text-blocks"> <%= render article.nested_elements.where(name: :text) %> </div> <% end %> ~~~ ## Element with tags Elements are taggable. To enable it, add `taggable: true` to the element's definition. ~~~ yaml - name: article taggable: true ingredients: - role: image type: Picture - role: headline type: Text as_element_title: true - role: copy type: Richtext ~~~ Tags are a collection on the `element` object. `element.tag_list` returns an array of tag names. ~~~ erb <%= element.tag_list.join(', ') %> ~~~ Alchemy uses the [gutentag](https://github.com/pat/gutentag) gem, so please refer to the github [README](https://github.com/pat/gutentag/blob/master/README.md) or the [Wiki](https://github.com/pat/gutentag/wiki) for further informations. ## Element with ingredient validations You can enable validations for your ingredients. They behave like the Rails model validations. Supported validations are * `presence` * `uniqueness` * `format` The `format` validator needs to have a [regular expression](http://rubular.com) or a predefined matcher string as its value. There are already predefined format matchers listed in the `config/alchemy/config.yml` file. It is also possible to add own format matchers there. ### Format matchers ~~~ yaml # config/alchemy/config.yml format_matchers: email: !ruby/regexp '/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/' url: !ruby/regexp '/\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix' ~~~ ### Example ~~~ yaml - name: person ingredients: - role: name type: Text validate: - presence: true - role: email type: Text validate: - format: email - role: homepage type: Text validate: - format: !ruby/regexp '/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/'] ~~~ The `email` ingredient gets validated against the predefined `email` matcher in the `config.yml`. The `homepage` ingredient is matched against the given regexp. ### Multiple validations. Ingredients can have multiple validations. ~~~ yaml - name: person ingredients: - role: name type: Text validate: - presence: true - uniqueness: true - format: name ~~~ The validations are evaluated in the order as they are defined in the list. At first the name ingredient will be validated for `presence`, then for `uniqueness` and at least against its `format`. ## Assign elements to page layouts Before you can use elements on pages, you need to define on which page layouts your element can be placed. So open `config/alchemy/page_layouts.yml` in your text editor and put the name of your new element into the list of available elements for a specific page layout. ~~~ yaml - name: standard elements: [article] autogenerate: [article] ~~~ You can now place the article element on each page with page layout `standard`. All future created pages with page layout `standard` will automatically create the article element for you. ## Generating the partials After typing the line below in your terminal, the rails generator will create the elements editor and view files. ~~~ bash bin/rails g alchemy:elements --skip ~~~ The `--skip` flag command skips files that already exist Without the skip flag, the generator will prompt you about over-writing your element view partials to include the content changes. The `--force` flag will overwrite your element view partials automatically. ::: tip You can pass `--template-engine` or `-e` as an argument to use `haml`, `slim` or `erb`. The default template engine depends on your settings in your Rails host app. ::: The generator will create partials for each element in your `app/views/alchemy/elements` folder. According to the first example, the article element, the generator will create the `_article.html.erb` partial. The generator does not only create these files, it also generates the necessary code for you. Mostly you can take use of the that code and make it nifty by adding some CSS stylings. ## Render elements in your layout Now that the above 'article' element example is associated with the 'standard' page layout, the element can be rendered on that layout `app/views/alchemy/page_layouts/_standard.html.erb`. ~~~ erb ... <div class="row"> <%= render_elements %> </div> ... ~~~ This renders all elements from current page. ::: tip Pages must be published for Elements to be associated and rendered. If you aren't seeing content you created in the admin interface, make sure elements are saved and and you click the "Publish current page content" button from the edit page admin view. ::: ### Render only specific elements Sometimes you only want to render specific elements on a specific page and maybe exclude some elements. ~~~ erb <body> <header><%= render_elements only: 'header' %></header> <main> <%= render_elements except: %w(header footer) %> </main> <footer><%= render_elements only: 'footer' %></footer> </body> ~~~ ### Render elements from other pages A common use case is to have global pages for header and footer parts: ~~~ yaml # config/alchemy/elements.yml - name: header hint: Navigation bar at the top of every page ingredients: # ... - name: footer hint: Footer section at the bottom of every page ingredients: # ... # config/alchemy/page_layouts.yml - name: header unique: true elements: [header] autogenerate: [header] layoutpage: true - name: footer unique: true elements: [footer] autogenerate: [footer] layoutpage: true ~~~ Which can be added to your `application.html.erb` file: ~~~ erb <!DOCTYPE html> <html lang="<%= @page.language_code %>"> <head> ... </head> <body> <header><%= render_elements from_page: 'header' %></header> <main> <%= yield %> </main> <footer><%= render_elements from_page: 'footer' %></footer> <%= render "alchemy/edit_mode" %> </body> </html> ~~~ ### Render a group of elements on a fixed place on the page Often you have a separate section on one page (like a sidebar) or a global section to be rendered on every page (like a navbar or footer). If you configure those elements as `fixed: true` in `elements.yml`, then they'll be separated from the general collection of elements and will be displayed separately in a separate tab in the admin elements section. ~~~ yaml {3} - name: sidebar unique: true fixed: true ingredients: - role: name type: Text # ... ~~~ You can then access these elements using the `fixed_elements` scope: ~~~ erb <% @page.fixed_elements.each do |element| %> <%= render_element(element) %> <% end %> ~~~ As `fixed_elements` is an a active record scope you can also filter by `where(name: 'your_element')` or use the `named('your_element')` scope. To gain some extra efficiency from Rails you could also use the collection rendering shortcut ~~~ erb <%= render @page.fixed_elements.named('sidebar') %> ~~~ ::: tip NOTE You need to use the elements view partial name instead of the element local variable in your child element views. Ie. `sidebar_view` instead of `element`. ::: ## Customizing the view partial The Alchemy element generator creates the basic html markup for you. Pretty useful, but maybe not what you need, sometimes. No problem, feel free to customize it. It's yours :). This is the newer notation for rendering the element's partial: ~~~ erb <%= element_view_for(element) do |el| %> <h3><%= el.render :headline %></h3> <div class="row"> <div class="large-6 columns"> <p> <%= el.render :image %> </p> </div> <div class="large-6 columns"> <p> <%= el.render :text %> </p> </div> </div> <% end %> ~~~ The `element_view_for` helper wraps the inner html code into a `div` element by default. You can pass arguments to the helper to change its rendering behavior: The second argument `tag` is used for the wrapping html tag. Passing `false` to it means no wrapping at all. Passing the name of any html element to it means the inner html gets wrapped within the given html tag instead of the default `div`. ### Pass options to the element wrapper You can pass additional arguments to add or change any html attributes of the wrapper. ~~~ erb <%= element_view_for(element, tag: 'li', class: 'red', id: 'my_unique_id') do |el| %> ... <% end %> ~~~ ::: tip If you want to learn more about the helper methods used in these partials, please have a look at the [Documentation](http://rubydoc.info/search/github/AlchemyCMS/alchemy_cms?q=helper). ::: ### Pass options to the ingredient view You can pass options to the ingredient view. ~~~ erb <%= element_view_for(element) do |el| %> <%= el.render :image, {size: '200x300'}, class: 'image-large' %> <% end %> ~~~ ::: tip Instead of passing the `size` of an image into the `EssencePicture` as shown above, you should consider to use static [ingredient settings](ingredients.html#individual-ingredient-settings) instead. ::: The first hash is the `options` the second one the `html_options` passed to the wrapper of the ingredient. If you only want to pass `html_options` you need to pass an empty hash as second argument. ~~~ erb <%= element_view_for(element) do |el| %> <%= el.render :image, {}, class: 'image-large' %> <% end %> ~~~ ::: tip Not all ingredients have wrapper tags. A list of all ingredient views are [here](https://github.com/AlchemyCMS/alchemy_cms/tree/master/app/views/alchemy/ingredients). ::: ## Translating elements Element and ingredient names are passed through Rails' [I18n](http://guides.rubyonrails.org/i18n.html) library. You can translate them in your `config/locales` language yml file. ~~~ yaml de: alchemy: element_names: contact_form: Kontaktformular search: Suche ingredient_roles: headline: Überschrift ~~~ Content names can also be translated related to their element. This is useful for ingredients with the same name that should have different translations. ~~~ yaml de: alchemy: element_names: contact_form: Kontaktformular ingredient_roles: color: Farbe contact_form: color: Button Farbe ~~~ ### Re-arranging Elements on a Page Collapsed elements can be re-arranged by clicking and dragging the its icon to the left of the element title. You can't re-arrange an expanded element, you need to collapse it first. ### Using the Clipboard The clipboard receives elements that are copied or cut. The most prominent use-case is to copy or move elements from one page to another. You can use the icons to copy and cut a specific element from its expanded state. You can view the contents of the clipboard using the icon at the top of the elements bar. When adding a new top-level element, the "Paste from clipboard" tab will be visible if the clipboard is storing any elements. --- URL: "/v7/getting_started.html" LLMS_URL: "/v7/getting_started.md" prev: false next: "about" --- # Getting Started This guide covers getting up and running with AlchemyCMS. After reading it, you should be familiar with: * Installing and creating a new application with AlchemyCMS * Creating a user and logging in for the first time. ## Prerequisites This guide is designed for beginners who want to get started with AlchemyCMS from scratch. It does not assume that you have any prior experience with AlchemyCMS. However, to get the most out of it, you need to have some prerequisites installed: * The [Ruby](http://www.ruby-lang.org/en/downloads) programming language * At least one of these databases: [SQLite](https://sqlite.org), [MariaDB](https://www.mariadb.org) or [PostgreSQL](https://www.postgresql.org) * The image processing library [ImageMagick](https://www.imagemagick.org/script/install-source.php) ### Installing dependencies **It is highly recommended** to use a package manager to install dependencies like ImageMagick and your database. Common package managers are: * [Homebrew](http://brew.sh) on the Mac * Your Linux box already has a package manager installed and you likely are already familiar with it. ### Installing Ruby The most common ways to install Ruby on your computer nowadays are: * If you are on a Mac or Linux Box use [RVM](http://rvm.io) * If you are on Windows use [RailsInstaller](http://railsinstaller.org) ### Learning Rails It is highly recommended to familiarize yourself with Ruby on Rails before using AlchemyCMS. You will understand much more and see things clearer if you know about the basics of Ruby on Rails. There are many excellent resources on the internet for learning Ruby on Rails, including: * [Try Ruby](http://tryruby.org) * [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html) * [Rails for Zombies](http://railsforzombies.org) * [Railscasts](http://railscasts.com) ## Installing Alchemy ### Create the Rails application The installation of AlchemyCMS is very easy. You just need to run Ruby's `gem` command. ~~~ bash gem install alchemy_cms ~~~ Alchemy is a Rails 4 engine, so at first you need to generate a fresh Rails 4 application, by running this command ~~~ bash rails new YOUR_APP_NAME ~~~ ::: tip INFO The `rails` command has lots of parameters like choosing the database you want to work with. Please follow [the official Rails guides](https://guides.rubyonrails.org/getting_started.html) for further information. ::: ### Install Alchemy into the Rails application In your existing Rails application, you can need to require the AlchemyCMS gem. Just add this line to your `Gemfile`. ~~~ ruby gem 'alchemy_cms' ~~~ Since AlchemyCMS is a mountable engine, you need to define the mountpoint in your `config/routes.rb` file. ~~~ ruby MyApp::Application.routes.draw do mount Alchemy::Engine => '/' end ~~~ ::: tip You can mount Alchemy on every route you want. `pages`, `cms`, what ever you want. Most of the time you go with the root route. ::: ::: warning NOTE Please be aware that Alchemy has a very strong catch all route. It is *highly recommended* that you mount Alchemy at last position in your app, so your existing routes are still available. ::: Now you need to run bundler for installing the dependencies. ~~~ bash bundle install ~~~ Then run the install task. ~~~ bash bin/rake alchemy:install ~~~ #### Authentication If you already have your own user class in your application, you have to tell Alchemy about it. Please follow [this guide](how_to_add_custom_authentication) to learn how to achieve that. If you don't have a authentication solution, you can use the alchemy-devise gem. Just add it to your apps `Gemfile` ~~~ ruby # Gemfile gem 'alchemy-devise' ~~~ ~~~ bash bundle install ~~~ and run the installer ~~~ bash bin/rails g alchemy:devise:install ~~~ ## Starting AlchemyCMS Now that you have AlchemyCMS successfully installed, let's move on by creating your first user with administrative privilegs. You just need to start a local ruby server on your development machine. ~~~ bash cd YOUR_APP_NAME ~~~ ~~~ bash bin/rails s ~~~ Open a browser window and navigate to [http://localhost:3000](http://localhost:3000). You will be greeted with a screen that is prompting you to create the first user. Congratulations, you can now access the backend. --- URL: "/v7/how_to_add_custom_authentication.html" LLMS_URL: "/v7/how_to_add_custom_authentication.md" --- # Custom Authentication Alchemy 3.0 has removed its build in authentication into a separate gem called [alchemy-devise](https://github.com/AlchemyCMS/alchemy-devise). Now, that this is not in the core anymore it gives you the possibility to add your own custom authentification. You only have to tell Alchemy about your user class. In this guide you will learn to: * add just enough code to make your User class work with Alchemy * add additionally methods to enhance the integration ## Activating your model Alchemy has some defaults for user model name and login logout path names: ### Defaults * `Alchemy.user_class_name` defaults to `'User'` * `Alchemy.login_path` defaults to `'/login'` * `Alchemy.logout_path` defaults to `'/logout'` Anyway, you can tell Alchemy about your authentication model configuration. ### Example ~~~ ruby # config/initializers/alchemy.rb Alchemy.user_class_name = 'Admin' Alchemy.login_path = '/auth/login' Alchemy.logout_path = '/auth/logout' ~~~ ## Mandatory configuration ### Relationships - `has_many :folded_pages` - This will allow alchemy to store page tree folding on a per user basis. ### Setting roles The only method that AlchemyCMS needs from your user class is `alchemy_roles`. This method has to return an array of strings containing at least one of these roles: * `member` * `author` * `editor` * `admin` ### Example ~~~ ruby # app/models/user.rb def alchemy_roles if admin? %w(admin) else [] end end ~~~ ::: tip You can use your own authorization system to set the role. You only need to ensure that it returns an Array with at least one of the roles. ::: That's it. Alchemy has everything it needs to use your user class as authentication model. ## Optional methods Although Alchemy does not need much to know about your user class, there are some few other attributes that are good to have in your user class which are: ### Name This would be used to say "Welcome back `#{@user.name}`" in the Alchemy dashboard. ### Example ~~~ ruby # app/models/user.rb def name # If you don't have a name, you could use the user's email read_attribute(:email) end ~~~ ### Display name This is used in the "logged in as" note on the top right corner of the admin interface. ### Example ~~~ ruby # app/models/user.rb def alchemy_display_name # If you don't have a name, you could use the user's email "#{firstname} #{lastname}".strip end ~~~ ## Preferred Language A locale String to set the users preferred translation of the Alchemy GUI. Anyway, the user has always the option to switch the language, but it is good to give your user its preferred language. ### Example ~~~ ruby # app/models/user.rb def language # Always use dutch as translation 'nl' end ~~~ ::: tip In a real world application you maybe want to store this as `alchemy_language` in the users table. ::: ## User stamp columns Alchemy stores updater and creator ids (if the columns are present). If you want to track which user updated a record you need to add: `creator_id` and `updater_id` to your users table. ### Example ~~~ bash bin/rails g migration add_creator_id_and_updater_id_to_users creator_id:integer:index updater_id:integer:index bin/rake db:migrate ~~~ ## Tagging If you want your users to be taggable you need to add the `acts_as_taggable` and `acts_as_tagger` class methods. ### Example ~~~ ruby class User < ActiveRecord::Base acts_as_taggable acts_as_tagger ... end ~~~ ::: tip You also should add a `cached_tag_list` text column into your users table for better performance. ::: ~~~ bash bin/rails g migration add_cached_tag_list_to_users cached_tag_list:text ~~~ This is it! Now your user class should be a first class citizen of the Alchemy admin backend. --- URL: "/v7/how_to_create_a_blog_template.html" LLMS_URL: "/v7/how_to_create_a_blog_template.md" --- # How To: Create a blog template A blog is an appropriate way to present information about formal and informal events or extraordinary experiences. In this guide you will learn how to: - Define blog layout and elements - Integration in your existing application ## Conceptual page and content structuring Imagine you like to have a simple blog template on your website. First of all as mentioned in [about AlchemyCMS](about) you should identify required components in your blog layout. Common components are a **headline**, a **blog post** itself and **sidebar blocks** for category overview and featured posts. Following the steps you will create blog template according to [Foundation Zurb](http://foundation.zurb.com/). (© ZURB, Inc) *We will build this template using the [Blog sample](http://foundation.zurb.com/page-templates/blog.html)* ## Concept and structure Visit the Blog sample page and have a deep look at the layout. Let us start to divide the template. We can identify the following page elements: - **blog post (multiple)** contains title, date, author, image and text - **categories block** contains list of categories - **featured block** contains text block with link to favorite blog post ## Prerequisites At the beginning some preparation is required. First of all, follow these instruction provided by Zurb to [Add Foundation to your Rails app](http://foundation.zurb.com/docs/applications.html). Hence the necessary preparations are completed. ## Define elements First of all we define the elements we need in `config/alchemy/elements.yml`. The individual page elements (see [Concept and structure](#concept-and-structure)) could be considered as an element. ### Blog title The blog should have a unique title. The title element contains a `title` and a `text` which represents a description or motto. In both cases we choose an `Text`, due to the fact that the `title` and the `text` will contain simple text. The `default` attribute provides the preset value, if the user didn't specify a motto or description (in Alchemy's backend). ~~~ yaml - name: blog_title unique: true ingredients: - role: title type: Text default: Blog - role: text type: Text default: This is my blog. It's awesome. ~~~ ### Blog post A `blog_post` element has a title, date, author, image and text. The title is represented by `blog_post_title`. It's just a text, so we took `Text` as type. Below `settings` we use `linkable: true`, because the user should be able to choose a link for the title of the blog post. The meta data like author and creation date corresponds to `blog_post_meta`. The `blog_post_intro_text` text as well as the main content `blog_post_content` get the type `Richtext`, since the user likes to add multiline paragraphs. The `blog_post_image` is of type `Picture`. This will empower the user to add an image. ~~~ yaml - name: blog_post ingredients: - role: blog_post_title type: Text default: Blog Post Title settings: linkable: true - role: blog_post_meta type: Text default: "Written by John Smith on August 12, 2012" - role: blog_post_intro_text type: Richtext - role: blog_post_image type: Picture - role: blog_post_content type: Richtext ~~~ ### Categories Block The 'categories block' has a headline and a list of available categories. The `categories_headline` is represented by an `Text`. In the backend the user should be able to add categories easily and dynamically. That's the reason why we chose the `nestable_elements` feature. ~~~ yaml - name: categories_block ingredients: - role: categories_headline type: Text default: Categories nestable_elements: - category - name: category ingredients: - role: name type: Text settings: linkable: true ~~~ ### Featured block The 'featured block' with a headline, text and a link. ~~~ yaml - name: featured unique: true ingredients: - role: featured_headline type: Text default: "Featured" - role: featured_text type: Richtext - role: featured_link type: Text settings: linkable: true ~~~ ::: tip Instead of `Text` you could alternatively use `Link` for 'featured_link'. ::: ## Define blog page layout After the definition of the elements, you we will continue with the defintion of an appropriate page layout. Page layout are specified in `config/alchemy/page_layouts.yml`. Add a new page layout for your blog template: ~~~ yaml - name: blog elements: - blog_title - blog_post - categories_block - featured autogenerate: - blog_title ~~~ The `autogenerate` attribute allows to define elements which will be generated automatically, when the user creates a new page in backend. ## Generate and customize the partials The command ~~~ bash rails g alchemy:elements --skip ~~~ creates the partials according to the element definition in the `elements.yml`. The partials are stored in `app/views/alchemy/elements/`. e.g. the partial for 'blog_post' element: `_blog_post.html.erb` ### Customize the partials In order to adapt the output of the elements you have to change the partial. The command above generates default partials. The `_blog_post.html.erb` might look like this: ~~~ erb <% cache(blog_post) do %> <%= element_view_for(blog_post) do |el| %> <div class="blog_post_title"> <%= el.render :blog_post_title %> </div> <div class="blog_post_meta"> <%= el.render :blog_post_meta %> </div> <div class="blog_post_intro_text"> <%= el.render :blog_post_intro_text %> </div> <div class="blog_post_image"> <%= el.render :blog_post_image %> </div> <div class="blog_post_content"> <%= el.render :blog_post_content %> </div> <% end %> <% end %> ~~~ #### `_blog_post_.html.erb` The blog post should match with the layout of the blog page in general, therefore we edit the partial: ~~~ erb <% cache(blog_post) do %> <%= element_view_for(blog_post) do |el| %> <article> <h3><%= el.render :blog_post_title %></h3> <h6><%= el.render :blog_post_meta %></h6> <div class="row"> <div class="large-6 columns"> <%= el.render :blog_post_intro_text %> </div> <div class="large-6 columns"> <%= el.render :blog_post_image %> </div> </div> <%= el.render :blog_post_content %> </article> <hr/> <% end %> <% end %> ~~~ For the remaining elements replace the content of the correspondig partials with the following code snippets: #### `_blog_title.html.erb` ~~~ erb <% cache(blog_title) do %> <%= element_view_for(blog_title, tag: 'h1') do |el| %> <%= el.render :title %> <small><%= el.render :text %></small> <% end %> <% end %> ~~~ #### `_categories_block.html.erb` ~~~ erb <% cache(categories_block) do %> <%= element_view_for(categories_block) do |el| %> <h5><%= el.render :categories_headline %></h5> <%= render categories_block.nested_elements %> <% end %> <% end %> ~~~ #### `_category.html.erb` ~~~ erb <% cache(category) do %> <%= element_view_for(category, tag: 'p') do |el| %> <%= el.render :name %> <% end %> <% end %> ~~~ #### `_featured.html.erb` ~~~ erb <% cache(featured) do %> <%= element_view_for(featured) do |el| %> <h5><%= el.render :featured_headline %></h5> <%= el.render :featured_text %> <p><%= el.render :featured_link %></p> <% end %> <% end %> ~~~ ## Embed the elements into your page layout Finally you have to integrate your elements into the page layout. ### Create new page layout view Create a new file called `_blog.html.erb` in `app/views/alchemy/page_layouts` and copy the plain html code from [Foundation Zurb Blog Template Raw](https://gist.githubusercontent.com/ghaida/5054379/raw/f080e139a4e25c4548b265e7229cd6dbe09bff92/gistfile1.html). ### Edit blog page layout view In `app/views/alchemy/page_layouts/_blog.html` search for (inside 'Nav Bar' comment) ~~~ erb <h1> Blog <small>This is my blog. It's awesome.</small> </h1> ~~~ and replace with: ~~~ erb <%= render_elements only: 'blog_title' %> ~~~ The **blog_title** set in the backend is now visible in the blog page layout context. Inside the commented 'Main Blog Content' replace the whole content of `<div class="large-9 columns" role="content">` with ~~~ erb <%= render_elements only: 'blog_post' %> ~~~ to embed the **blog_post** elements into the page layout view. Inside 'Sidebar' comment replace ~~~ erb <h5>Categories</h5> <ul class="side-nav"> <li> <a href="#">News</a> </li> <li> <a href="#">Code</a> </li> <li> <a href="#">Design</a> </li> <li> <a href="#">Fun</a> </li> <li> <a href="#">Weasels</a> </li> </ul> ~~~ with ~~~ erb <%= render_elements only: 'categories_block' %> ~~~ and replace the content of `<div class="panel">` with ~~~ erb <%= render_elements only: 'featured' %> ~~~ Now you can run ~~~ bash rails server ~~~ to start the server. --- URL: "/v7/how_to_create_a_contact_form.html" LLMS_URL: "/v7/how_to_create_a_contact_form.md" --- # How To: Create a contact form This guide covers the configuration setup and create process of a regular contact form: - Setup Alchemy mailer - Customize the views ## Additional Rails and application setup Alchemy relies on Rails' [ActionMailer](https://guides.rubyonrails.org/action_mailer_basics.html) to send emails. You will need to configure ActionMailer to use an SMTP service like Gmail, Sendgrid, Mailgun, Mandrill, Amazon SES, etc. ## Mailer Settings This is the default mailer configuration in `config/alchemy/config.yml`: ~~~ yaml mailer: page_layout_name: contact forward_to_page: false mail_success_page: thanks mail_from: your.mail@your-domain.com mail_to: your.mail@your-domain.com fields: [salutation, firstname, lastname, address, zip, city, phone, email, message] validate_fields: [lastname, email] ~~~ - **page_layout_name** `String` (Optional) A `Alchemy::PageLayout` name. Used for rendering the contact form. - **forward_to_page** `Boolean` (Optional) If set to true the mailer redirects to the page defined with mail_success_page option. - **mail_success_page** `String` (Optional) A page urlname that should be displayed after successfully sending the mail. - **mail_from** `String` (Optional) The email address the mail is send from. - **mail_to** `String` (Optional) The email address the mail is send to. - **fields** `Array` Field names of your contact form. These fields become attributes on the `Alchemy::Message` Model - **validate_fields** `Array` Field names of your contact form that should be validated for presence. ::: tip NOTE All optional setting should be manageable through the content management user. So creating an element that set these values is **highly recommended**. ::: ## Creating a contact form element Describe a new Element with this options inside your `elements.yml` file: ~~~ yaml - name: contactform ingredients: - role: mail_from type: Text validate: - presence - format: email - role: mail_to type: Text validate: - presence - format: email - role: subject type: Text validate: - presence - role: success_page type: Page validate: - presence ~~~ ::: tip NOTE The fields `mail_to`, `mail_from`, `subject` and `success_page` are recommended. The `Alchemy::MessagesController` uses them to send your mails. That way your customer has full control over these values inside his contact form element. ::: ::: warning INFO The validations are optional, but **highly recommended**. ::: Create a page layout for your contact page in the `page_layouts.yml` file: ~~~ yaml - name: contact unique: true cache: false elements: - pageheading - heading - contactform autogenerate: - contactform ~~~ ::: warning INFO Disabling the page caching is **very important**! ::: ## Example contact form Use the `rails g alchemy:elements --skip` generator to create the view files. ### The contact form view We are using the great `simple_form` gem in this example. If this gem is not already installed, you have to add ~~~ bash bundle add simple_form ~~~ Then open `app/views/alchemy/elements/_contactform.html.erb` in your text editor and replace the content with: ~~~ erb <%= simple_form_for(@message ||= Alchemy::Message.new) do |form| %> <%= form.input :firstname %> <%= form.input :lastname %> <%= form.input :email %> <%= form.input :message, as: 'text' %> <%= form.hidden_field :contact_form_id, value: element.id %> <%= form.button :submit %> <% end %> ~~~ ::: warning INFO See the `hidden_field`? This is important, or the messages mailer can't do all the magic for you. ::: If you use different or additional input symbols like 'company' , 'age' etc. make sure to adapt the fields in the mailer configuration (`config/alchemy/config.yml`). ::: tip Please have a look at the [simple form documentation](https://github.com/plataformatec/simple_form#readme) for further infos about the various config options. ::: ## Translating validation messages All validation messages are passed through `::I18n.t` so you can translate it in your language yml file. ### Example Translation ~~~ yaml de: activemodel: attributes: alchemy/message: firstname: Vorname lastname: Nachname ~~~ If you would like to use same vocabulary in different context: e.g in the `app/views/alchemy/elements/_contactform_view.html.erb` ~~~ erb <%= form.input ... %> <%= form.input :firstname , label: t(:sender, scope: 'my_forms.contactform') %> <%= form.input ... %> ~~~ Your language yml should look like this: (`config/locales/de.yml`) ~~~ yaml de: my_forms: contactform: sender: Sender ~~~ Now you can reuse the label by using the `t` function while defining label. --- URL: "/v7/how_to_create_custom_ingredients.html" LLMS_URL: "/v7/how_to_create_custom_ingredients.md" --- # Creating own Ingredients Alchemys powerful content storage technology is build around ingredients. They are ActiveRecord models that have an editor and a presentation view partial. Alchemy has lots of [build in ingredients](https://github.com/AlchemyCMS/alchemy_cms/tree/main/app/models/alchemy/ingredients). But you can create your own ingredient and even associate them with your existing models. This guide shows how. * Use the generator to create a new ingredient * Associate an ingredient to your model ## Using The Ingredient Generator The ingredient generator is a wrapper around the Rails model generator. It generates the ingredient model for you. ~~~ bash bin/rails g alchemy:ingredient MyIngredient ~~~ ::: tip INFO Ingredients are created under the `Alchemy::Ingredients` namespace. ::: This is what the generated model looks like: ~~~ ruby # app/models/alchemy/ingredients/my_ingredient.rb module Alchemy module Ingredients class MyIngredient < Alchemy::Ingredient # Set additional attributes that get stored in the `data` JSON column # store_accessor :data, :some, :attribute # Set a related_object alias for convenience # related_object_alias :some_association_name, class_name: "Some::Klass" end end end ~~~ Alchemy stores the main value in the `value` (a `Text`) column. If you want to store additional values in another column, please add it as attribute to the `data` [`store_accessor`](https://api.rubyonrails.org/classes/ActiveRecord/Store.html). Rails will create accessor methods for you. ## The ingredient views Every ingredient has to have two views: * One for presenting * One for editing ### The view partial The view partial is used by the `render_elements` helper to present the ingredient to the user. It is yours. Adjust it to your needs. You can access the value with the `ingredient` method of the `content` object instance. ~~~ erb <!-- app/views/alchemy/ingredients/_my_ingredient_view.html.erb --> <h1><%= my_ingredient_view.ingredient %></h1> ~~~ ### The editor partial The editor partial is basically a set of form fields holding values of your ingredient. It is rendered inside the element editor view form object. This is just what the generator creates for you: ~~~ erb <!-- app/views/alchemy/ingredients/_my_ingredient_editor.html.erb --> <%= content_tag :div, class: my_ingredient_editor.css_classes, data: my_ingredient_editor.data_attributes do %> <%= element_form.fields_for(:ingredients, my_ingredient_editor.ingredient) do |f| %> <%= ingredient_label(my_ingredient_editor) %> <%= f.text_field :value %> <% end %> <% end %> ~~~ But this is yours. Feel free to adjust it to your needs. Just make shure that you provide form fields that Alchemy can use to update your object in the database. ## Associations You can associate every ActiveRecord based model with an ingredient. In this example we want to connect an existing `Person` model to an element, so we can associate it with an Alchemy page. Just use the `related_object_alias` method to tell Alchemy the foreign key to use for the association. ### Set the foreign key ~~~ ruby # app/models/alchemy/ingredients/person.rb module Alchemy module Ingredients class Person < Alchemy::Ingredient related_object_alias :person, class_name: "My::Person" end end end ~~~ ### Accessing your model instance That's it. Everything else is handled by Alchemy. You can now access the associated `My::Person` model with the `person` method. ~~~ erb <!-- app/views/alchemy/ingredients/_person_view.html.erb --> <%= person_view.person.firstname %> ~~~ Now you can use your new ingredient in any element you want as shown in the [Elements guide](elements). --- URL: "/v7/how_to_create_modules.html" LLMS_URL: "/v7/how_to_create_modules.md" --- # Creating custom Modules Basicly there are two ways to set up a module: * Use Alchemy as your Rails admin backend * Create a new Engine ## Generator Alchemy provides a rails generator for custom modules, that can be invoked like this ~~~ bash bin/rails generate alchemy:module your_module_name ~~~ ## Using Alchemy as your Rails admin backend Hence every module is a subsection of a Rails application or a gem you can roll out your own admin backend if you like. By using Alchemy's backend interface you get a whole bunch of things: 1. Authentication and Authorization 2. A highly and fine grained customizable resource manager (CRUD interface) 3. A nicely styled user interface And it's easy as this: ~~~ ruby module Admin class YourResourcesController < Alchemy::Admin::ResourcesController end end ~~~ You can overwrite the default controller actions and views as you like. An instance-variable named after the resource (i.e. `@your_resource` for `YourResource`) is defined for use in your views. After you setting up your routes you have to take care for control access: ## Authorization Rights and roles are set in a CanCan Ability model. Create the file if it doesn't exist already. It has to be registered manually, by invoking `Alchemy.register_ability MyModuleAbility` in the module initializer. ~~~ ruby # app/models/my_module_ability.rb class MyModuleAbility include CanCan::Ability def initialize(user) if !user.blank? && user.is_admin? can :manage, YourResource can :manage, :admin_your_resources end end end ~~~ More information about authorization can be found in the docs of the [`cancancan` gem](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities) ## Routing ### In an engine ~~~ ruby # config/routes.rb YourApp::Application.routes.draw do ... mount YourAlchemyModule::Engine => '/' mount Alchemy::Engine => '/' end ~~~ ### In your app ~~~ ruby # config/routes.rb YourApp::Application.routes.draw do resources :your_resource namespace :admin do resources :your_resource end mount Alchemy::Engine => '/' end ~~~ ::: tip Due to Alchemy's strong routes it is **strongly recommended** to mount your engine before Alchemy. ::: ## Registering the module Last but not least the module needs to be registered to AlchemyCMS. ### When using your host app ~~~ ruby # my_host_app/config/initializers/alchemy_modules.rb Alchemy::Modules.register_module({ name: 'name_of_your_module', order: 1, # The position in main navigation, if you have more than 1 module. navigation: { name: 'modules.products', # The name in the main navigation (translated via I18n). controller: '/admin/products', # The controller that will be used. action: 'index', # The controller action that will be used. icon: 'module_icon', # Class of icon that will be rendered as navigation icon. sub_navigation: [ { name: 'modules.products', # The name for the subnavigation tab (translated via I18n). controller: '/admin/products', # Controller that will be used. action: 'index' # Controller action that will be used. }, { name: 'modules.variants', # The name for the subnavigation tab (translated via I18n). controller: '/admin/variants', # Controller that will be used. action: 'index' # Controller action that will be used. } ] } }) # Register the module ability Alchemy.register_ability MyModuleAbility ~~~ NOTE: As an alternative to the module icon you can pass: `image: '/alchemy/icon_of_your_module.png'` ### Using a Rails engine ~~~ ruby # my_engine/config/initializers/alchemy.rb Alchemy::Modules.register_module({ name: 'name_of_your_module', engine_name: 'name_of_your_engine' # The engine_name set in your Rails::Engine class. order: 1, # The position in main navigation, if you have more than 1 module. navigation: { name: 'modules.products', # The name in the main navigation (translated via I18n). controller: '/admin/products', # The controller that will be used. action: 'index', # The controller action that will be used. icon: 'module_icon', # Class of icon that will be rendered as navigation icon. sub_navigation: [ { name: 'modules.products', # The name for the subnavigation tab (translated via I18n). controller: '/admin/products', # Controller that will be used. action: 'index' # Controller action that will be used. }, { name: 'modules.variants', # The name for the subnavigation tab (translated via I18n). controller: '/admin/variants', # Controller that will be used. action: 'index' # Controller action that will be used. } ] } }) # Register the module ability Alchemy.register_ability MyModuleAbility ~~~ ## Translate your module names All module names are passed through I18n within a `alchemy` namespace. The name will be used as translation key. You should namespace your module name (with i.e. `modules`), to prevent conflicts. ### Example Given a module name `modules.products` your translation has to be: ~~~ yaml # config/locales/de.yml de: alchemy: modules: products: Produkte ~~~ ## Customizing the built in views Alchemy makes use of Rails templates and partials to render your model's resource views. You can overwrite each of them in your app or engine. Just place a file with the same name in your `app/views/admin/plural-name-of-your-resource/` folder and Rails will pick that up instead of the Alchemy one. You can find the list of all templates and partials Alchemy provides [on GitHub](https://github.com/AlchemyCMS/alchemy_cms/tree/7.2-stable/app/views/alchemy/admin/resources). ### The resource form Alchemy uses [a `_form` partial](https://github.com/AlchemyCMS/alchemy_cms/blob/7.2-stable/app/views/alchemy/admin/resources/_form.html.erb) to render the fields for your resource's edit view. It looks like this ```erb <%= alchemy_form_for resource_instance_variable, url: resource_path(resource_instance_variable, search_filter_params) do |f| %> <% resource_handler.editable_attributes.each do |attribute| %> <% if relation = attribute[:relation] %> <%= f.association relation[:name].to_sym, collection: relation[:collection], label_method: relation[:attr_method], include_blank: Alchemy.t(:blank, scope: 'resources.relation_select'), input_html: {is: 'alchemy-select'} %> <% elsif attribute[:type].in? %i[date time datetime] %> <%= f.datepicker attribute[:name], resource_attribute_field_options(attribute) %> <% elsif attribute[:enum].present? %> <%= f.input attribute[:name], collection: attribute[:enum], include_blank: Alchemy.t(:blank, scope: 'resources.relation_select'), input_html: {is: 'alchemy-select'} %> <% else %> <%= f.input attribute[:name], resource_attribute_field_options(attribute) %> <% end %> <% end %> <% if f.object.respond_to?(:tag_list) %> <%= render Alchemy::Admin::TagsAutocomplete.new do %> <%= f.input :tag_list, input_html: { value: f.object.tag_list.join(",") } %> <% end %> <% end %> <%= f.submit Alchemy.t(:save) %> <% end %> ``` To customize the form place a file with the same content into your apps view folder and adjust it to your needs. ### The resource table Alchemy uses [a `_table` partial](https://github.com/AlchemyCMS/alchemy_cms/blob/7.2-stable/app/views/alchemy/admin/resources/_table.html.erb) to render the table for your resource's index view. The default table looks like this ```erb <% if resources_instance_variable.any? %> <table class="list" id="<%= resource_handler.resources_name %>_list"> <thead> <tr> <% if local_assigns[:icon] %> <th class="icon"></th> <% end %> <% resource_handler.sorted_attributes.each do |attribute| %> <th class="<%= attribute[:type] %> <%= attribute[:name] %>"> <%= sort_link [:resource_url_proxy, @query], sortable_resource_header_column(attribute), resource_handler.model.human_attribute_name(attribute[:name]), default_order: attribute[:type].to_s =~ /date|time/ ? 'desc' : 'asc' %> </th> <% end %> <th class="tools"></th> </tr> </thead> <tbody> <%= render_resources(icon: local_assigns[:icon]) %> </tbody> </table> <% elsif search_filter_params[:q].present? %> <p><%= Alchemy.t('Nothing found') %></p> <% end %> <%= paginate resources_instance_variable, scope: resource_url_proxy, theme: 'alchemy' %> ``` You can customize it by adding a `_table.html.erb` partial into your resources view folder of your app or engine and adjust it accordingly. ### Examples Alchemy dogfeeds it's own resource views to render it's admin interface. You can take them as example as starting point. --- URL: "/v7/how_to_customize_tinymce.html" LLMS_URL: "/v7/how_to_customize_tinymce.md" --- # Customizing the Richtext Editor This guide explains how to override the default settings of the TinyMCE richtext editor and customize it. After reading this guide you will be able to: * [Overriding default settings](#override-the-default-settings) * [Customizing the toolbar](#custom-toolbar-buttons) * [Changing the default stylesheet](#setting-a-custom-stylesheet) ## Override the default settings To override the default settings you have to create a `tinymce.rb` initializer file into `config/initializers`. Alchemys default settings for TinyMCE can be found in the [documentation](https://www.rubydoc.info/github/AlchemyCMS/alchemy_cms/Alchemy/Tinymce) ### A Minimal Editor Let's reduce the options for formatting the text to a minimum. Because Alchemy merges the default options and your custom options you just need to pass the options you want to override to `Alchemy::Tinymce.init`. ~~~ ruby # config/initializers/tinymce.rb Alchemy::Tinymce.init = { toolbar: [ 'bold italic underline', 'pastetext charmap code | undo redo | alchemy_link unlink | alignleft aligncenter alignright alignjustify' ] } ~~~ Now only this three basic formatting options are available in the first toolbar to your editors. In the second toolbar are now the align formatting options available. ::: warning NOTE Overriding options always in the initializer applies to all richtext editors in all elements in Alchemy. ::: ## Custom toolbar buttons You can customize the TinyMCE toolbar with a lot of buttons. Find them in the [TinyMCE documentation](http://www.tinymce.com/wiki.php/Controls) ### Adding a format select box Let's add a format select to our editors so they can insert headlines into their texts. In the TinyMCE documentation we found our configuration option: ~~~ js tinymce.init({ ... block_formats: "Paragraph=p;Header 1=h1;Header 2=h2;Header 3=h3" }); ~~~ We just have to _convert_ this javacript object into a ruby hash. And we need to insert the format select into the set of buttons. ~~~ ruby # config/initializers/tinymce.rb Alchemy::Tinymce.init = { toolbar: [ 'bold italic underline | strikethrough subscript superscript | numlist bullist indent outdent | removeformat | fullscreen', 'pastetext | formatselect | charmap code | undo redo | alchemy_link unlink' ], block_formats: "Header 2=h2;Paragraph=p" } ~~~ That's it. Now the editor can format a block of text as headline 2. Nice! ::: warning NOTE Custom buttons always appear in all richtext editors in all elements in Alchemy. ::: ## Adding editor plugins It is possible to extend the tinymce editor with plugins. All you have to do is to download the tinymce plugin and copy the folder to `vendor/assets/javascripts/tinymce/plugins` and add the following content to `config/initializers/tinymce.rb` ~~~ ruby Alchemy::Tinymce.init = { plugins: Alchemy::Tinymce.plugins + ['colorpicker'], toolbar: [ 'bold italic underline | strikethrough subscript superscript | numlist bullist indent outdent | removeformat | alignleft aligncenter alignright | fullscreen', 'pastetext charmap hr | undo redo | alchemy_link unlink anchor | code | colorpicker' ] } ~~~ Another example configuration could be: ~~~ ruby Alchemy::Tinymce.plugins += ['textcolor'] Alchemy::Tinymce.init = { toolbar: [ 'bold italic underline | strikethrough subscript superscript | numlist bullist indent outdent | removeformat | fullscreen', 'pastetext | charmap code | undo redo | alchemy_link unlink | help | forecolor backcolor' ], } ~~~ This example adds the textcolor plugin and adds its button to the toolbar. ## Setting a custom stylesheet It is a good practice to set the stylesheet TinyMCE uses to display text in the editor area to the one you use to render the website. ::: tip INFO Please read more on custom stylesheets in the [TinyMCE documentation](http://www.tinymce.com/wiki.php/Configuration:content_css) ::: ### Create the css file Put a `tinymce_content.css` stylesheet into your `app/assets/stylesheets` folder. ### Setting the stylesheet ~~~ ruby # config/initializers/tinymce.rb Alchemy::Tinymce.init = { content_css: "/assets/tinymce_content.css" } ~~~ ### Compile the asset for production In production mode (on your server) Rails uses compiled assets to speed up the rendering of your page. As default Rails only compiles the `application.css` stylesheet. But you can tell Rails to compile our custom TinyMCE stylesheet as well. Open the `config/environments/production.rb` file in your editor and insert this line: ~~~ ruby config.assets.precompile += %w( tinymce_content.css ) ~~~ ## Per element customization You can also set TinyMCE configuration options per element. ~~~ yaml # elements.yml - name: minimal_text ingredients: - role: text type: Richtext settings: tinymce: toolbar: - 'bold italic underline', - 'pastetext charmap code | undo redo | alchemy_link unlink | alignleft aligncenter alignright alignjustify' ~~~ ## Configuration Syntax You can set any [TinyMCE configuration option](http://www.tinymce.com/wiki.php/Configuration). Just remember to _convert_ the javascript syntax into ruby. ### Convertions Tips Javascript and Ruby has very similiar syntax. Especially if you use the new Ruby 1.9 Hash syntax. So converting is no big deal. #### Object to Hash ~~~ js { "key" : "value" } => { key: "value" } ~~~ #### Array to Array Arrays have the same syntax. So you can just take them as they are. ~~~ js ["1", "2", "3", "4"] => ["1", "2", "3", "4"] ~~~ #### Object with Array as value ~~~ js { "key" : ["value1", "value2"] } => { key: ["value1", "value2"] } ~~~ #### Object with Object as value ~~~ js { "key" : { "value" : ["value1", "value2"] } } => { key: { value: ["value1", "value2"] } } ~~~ --- URL: "/v7/how_to_deploy_on_heroku.html" LLMS_URL: "/v7/how_to_deploy_on_heroku.md" --- # Heroku deployment [Heroku](https://heroku.com) is a cloud platform as a service. You can start with a free plan and run it for lifetime. In this guide you will learn how to deploy your existing Alchemy Site on Heroku. After finishing this guide you will know: * How to prepare your local Alchemy installation for Heroku * How to deploy code changes to Heroku ## Prerequisites We assume you already have a running Alchemy application on your local machine. In the following steps we call it `my-project`. Please replace it name with your actual name. All following shell commands need to be executed within your applications folder from the command shell. We also assume you already have an account on Heroku. [Otherwise signup now.](https://signup.heroku.com) You need the [Heroku Toolbelt](https://devcenter.heroku.com/articles/getting-started-with-ruby#set-up) for your OS installed to get access to the Heroku Command Line utility. Login with the heroku command from your command shell as described in the devcenter. The last thing we assume is you already have an Amazon AWS account. [Otherwise signup now.](https://aws.amazon.com) ## Amazon (AWS) setup ### Create S3 buckets Heroku does not provide data storage on its filesystem (beside a small amount for the application code and some temporary data). So we have to store our pictures and attachments outside of Heroku. Amazon S3 comes for the rescue. We need two S3 buckets, one for Alchemy pictures and another for Alchemy attachments. Please create two S3 buckets now and name them as follows: * `my-project-alchemy-pictures` * `my-project-alchemy-attachments` Please make sure to save the keys (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) Amazon gives you and keep them safe. ## Heroku setup ### Create a new application Create a [new Application from your Dashboard](https://dashboard.heroku.com/new). ### Install Memcache add-on We use Memcache to store and deliver cropped pictures and the page cache. This is important because otherwise page publishing and image cropping will not work accordingly. Lets add MemCachier to the application. ~~~ bash heroku addons:create memcachier:dev ~~~ This installs MemCachier with a 25M cache store for free. ### Set environment variables Run the following commands on your command shell to set some environment variables on your heroku account to provide the S3 key and bucket names. Make sure to replace `my-project` and `your-key` with the correct information. ~~~ bash heroku config:set AWS_REGION=bucket-region heroku config:set AWS_ACCESS_KEY_ID=your-key-id heroku config:set AWS_SECRET_ACCESS_KEY=your-key heroku config:set AWS_PICTURES_BUCKET=my-project-alchemy-pictures heroku config:set AWS_ATTACHMENTS_BUCKET=my-project-alchemy-attachments ~~~ ## Code adjustments ### Add gems We need to add a few gems to the `Gemfile` of the Application. ~~~ ruby gem 'thin' group :production do gem 'pg' gem 'rails_12factor' gem 'rack-cache', require: 'rack/cache' gem "dragonfly-s3_data_store" gem 'dalli' gem 'kgio' end ~~~ Now bundle the gems ~~~ bash bundle install ~~~ ::: tip Heroku uses PostgreSQL. If you want to use a different database for your development environment you need to define `group: "development"` for the relevant database adapter gem. ::: ### Use thin webserver instead of webrick Webrick is not the best choice for running rails applications in production. Lets tell Heroku we want to use thin instead. Create a new file in your applications root directory named `Procfile` and paste the following line. ~~~ ruby web: bundle exec thin start -p $PORT -e $RACK_ENV ~~~ ### Create initializer Create a new initializer `config/initializers/alchemy_file_storage.rb` in your application code to overwrite the information how and where Alchemy pictures and attachments are stored. You can just paste the following code and adjust the region if necessary. Be aware to use the same region as you did when creating the buckets. We set up the application to use Amazon S3 storage on production while using the filesystem in other environments. ~~~ ruby if Rails.env.production? require 'dragonfly' require 'dragonfly/s3_data_store' aws_defaults = { access_key_id: Rails.configuration.aws_access_key_id, secret_access_key: Rails.configuration.aws_secret_access_key, region: Rails.configuration.aws_region, storage_headers: {'x-amz-acl' => 'public-read'}, url_scheme: 'https' } Dragonfly.app(:alchemy_pictures).configure do plugin :imagemagick datastore :s3, { bucket_name: Rails.configuration.aws_pictures_bucket }.merge(aws_defaults) end Dragonfly.app(:alchemy_attachments).configure do datastore :s3, { bucket_name: Rails.configuration.aws_attachments_bucket }.merge(aws_defaults) end end ~~~ ### Configure rack-cache for production environment ~~~ ruby # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like # NGINX, varnish or squid. config.action_dispatch.rack_cache = true # use the Dalli client for its cache-store config.cache_store = :dalli_store client = Dalli::Client.new((ENV["MEMCACHIER_SERVERS"] || "").split(","), :username => ENV["MEMCACHIER_USERNAME"], :password => ENV["MEMCACHIER_PASSWORD"], :failover => true, :socket_timeout => 1.5, :socket_failure_delay => 0.2, :value_max_bytes => 10485760) config.action_dispatch.rack_cache = { :metastore => client, :entitystore => client } config.static_cache_control = "public, max-age=311040000" ~~~ ### Make S3 informations accessible to Rails configuration Paste the following lines to your `config/environments/production.rb` inside the configure block. The Rails application gets access to the S3 information provided via environment variables. ~~~ ruby config.aws_region = ENV['AWS_REGION'] config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID'] config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] config.aws_pictures_bucket = ENV['AWS_PICTURES_BUCKET'] config.aws_attachments_bucket = ENV['AWS_ATTACHMENTS_BUCKET'] ~~~ ### Prepare your seeds.rb In order to populate the remote database with basic information (which is needed for running Alchemy) please paste the following to your `db/seeds.rb`. You can skip this step if you already have it. ~~~ ruby Alchemy::Seeder.seed! ~~~ ## Git repository ### Create a new repo If your application code is already checked-in to a git repository you can skip this. Otherwise initialize a new git repository now. ~~~ bash git init ~~~ ### Make sure you do not store certain files in your git repo We do not want certain files in git. Please make sure you ignore at least following files/folders in your `.gitignore` file. ~~~ bash /tmp/* /log/* /uploads /public/assets /public/pictures /config/database.yml .DS_Store /db/*.sqlite3 ~~~ ### Commit your files to the git repository ~~~ bash git add . git commit -am "Initial commit" ~~~ ::: tip Never store any critical informations like database or ssh passwords in your git repository, even if you have a private one. ::: ### Add heroku as a git remote You need to add heroku as a remote git repository. Each time you push the master branch to heroku the application will be deployed. ~~~ bash heroku git:remote -a my-project ~~~ ## Deployment ### First deployment Lets deploy the application to heroku the first time! Its easy as pushing our repository to the heroku git remote. ~~~ bash git push heroku master ~~~ ### Migrate and seed the database We need to migrate the database and populate it with Alchemy seed data. ~~~ bash heroku run rake db:migrate heroku run rake db:seed ~~~ ### Verify the deployment Open a browser and enter the domain Heroku gave you. Usually its a subdomain consisting of your project name and the domain herokuapp.com (e.g.: https://my-project.herokuapp.com) ::: tip You can always get your domain from the settings on Heroku. E.g.: https://dashboard.heroku.com/apps/my-project/settings (replace my-project with the correct name) ::: ### Deploy the app later on Each time you want to deploy to Heroku just push your git commits to the heroku remote: ~~~ bash git push heroku master ~~~ --- URL: "/v7/how_to_extend_alchemy.html" LLMS_URL: "/v7/how_to_extend_alchemy.md" --- # Extending Alchemy At some point you may come across the need of adding some behavior to already existing controllers or models, that Alchemy provides. Obviously you could just copy the files, insert them into your projects and edit them there, but this isn't a really clean solution. * This section will give you a hint on how to extend existing Alchemy files without overriding everything. ## Tell Rails to load our extensions Tell Rails to load our extensions by adding some lines into `config/application.rb` ~~~ ruby # config/application.rb config.to_prepare do Dir.glob(Rails.root.join('app', '**', '*_extension.rb')) do |f| Rails.configuration.cache_classes ? require(f) : load(f) end end ~~~ ## Adding an extension After you set up the loading for our extensions, we can actually start making some. Lets assume you want to add a before_action method to the `Alchemy::PagesController`. You go into your host app and add a file in `app/controllers/alchemy/` called `pages_controller_extension.rb`. It's important to add the `_extension` to the filename so Rails will load them. Into this file you add the following code: ~~~ ruby module AlchemyPagesControllerExtension end Alchemy::PagesController.prepend AlchemyPagesControllerExtension ~~~ Into this block you can add anything you want your controller to do. As already mentioned, we want to add a before_action to this controller for some show off. It could then look like this: ~~~ ruby module AlchemyPagesControllerExtension def prepended(base) base.before_action :some_method, only: :show end end ~~~ The last step is adding some_method to your `ApplicationController`. This is done like you are used to in Rails. --- URL: "/v7/ingredients.html" LLMS_URL: "/v7/ingredients.md" prev: "elements" next: "render_images" --- # Ingredients ## Overview Ingredients store the actual content of your site. They are stored as instances of `Alchemy::Ingredient` subclasses on each `Alchemy::Element` instance. Alchemy uses Rails [Single Table Inheritance](https://guides.rubyonrails.org/association_basics.html#single-table-inheritance-sti) to store the values in the same table. Alchemy comes with a lot of predefined ingredients for the regular needs of a website project. Combine them like a chemestry kit into [elements](elements). Ingredients are Rails models. It is pretty easy to [add your own ingredient class](how_to_create_custom_ingredients) as well. ## Definition Ingredients are defined as `ingredients` on an [element definition](elements#defining-elements). ### Global ingredient settings When defining ingredients, you need to provide a `role` and `type`. You can set hints and default values as well. ~~~ yaml # config/alchemy/elements.yml - name: article ingredients: - role: headline type: Headline hint: This is the headline - role: color type: Text settings: input_type: color - role: copy type: Richtext default: Lorem ipsum dolor as_element_title: true ~~~ * **role** `String` _required_ A lowercased **unique** (per element) role of the ingredient. Separate words needs to be underscored. * **type** `String` _required_ An type this ingredient is of. Alchemy has lots of built in ingredients for [simple text](#text), [rich text](#richtext), [images](#picture), [booleans](#boolean) and more. * **hint** `String|Symbol|Boolean` A hint for the user in the admin frontend that describes what the ingredient is used for. The hint is translatable if you provide an I18n translation `Symbol` instead of a `String`. You may also set it to `true` to default to the I18n key `alchemy.ingredients_hints.your_ingredient_role`. * **default** `String` The default text to prefill newly created elements. You may also use a symbol to set it to the I18n key `alchemy.default_ingredients_texts.your_ingredient_role` * **as_element_title** `Boolean` For the displayed element title, the first ingredient is used. Use this setting to override this behaviour and show other ingredient as element title. * **settings** `Hash` A set of options to configure the ingredient. Each ingredient has its own set of options listed below. ### Individual ingredient settings Each ingredient type can have its own type of settings. To configure these settings you have to pass them into its `settings` key in the `elements.yml`. ~~~ yaml {5-6} - name: my_element ingredients: - role: headline type: Text settings: linkable: true ~~~ ## Text Stores plain text of 255 chars max. Use this for a headline, or a product name. The editor is renderd as a single lined input field. The view output will be sanitized and HTML escaped. ### Settings * **linkable** `Boolean` If set to `true`, the user can add a link to the text. * **input_type** `String` Change the [input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types) of the form field displayed in the editor. ### Example ~~~ yaml - name: button type: Text settings: linkable: true ~~~ ## Headline Stores headline text of 255 chars max. Use this for HTML headings. The editor is renderd as a single lined input field with level and optional size selects. The view output will be a heading element. ### Settings * **anchor** `Boolean` or the `String` `from_value` If set to `true`, the user can add a custom anchor to the headline. If set to `from_value` the anchor will be created from the value. The anchor will be added as `id` to the heading tag. * **levels** `Array` of `Integer`s Set the available [heading levels](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements) that the editor can choose from. By default it is 1 to 6. * **sizes** `Array` of `Integer`s Optionally add a listy of sizes that the editor can choose a heading css size. Added as class (`.h2`) to the heading element. Use this of ### Example ~~~ yaml - name: headline type: Headline settings: levels: [2, 3] sizes: [1, 2] ~~~ ## Richtext Used to store paragraphs of formatted text. The editor is rendered as a textarea with embedded Tinymce Editor. ### Settings You can customize the Tinymce editor of a single element instance. * **tinymce** `Hash` ### Example ~~~ yaml - name: text type: Richtext settings: tinymce: style_formats: - title: 'Subheadline' block: 'h3' ~~~ ::: tip INFO See the [Tinymce customization guide](how_to_customize_tinymce) for all available options ::: ## Picture Store references to pictures the editor assigns from the library. The editor partial is rendered as a picture editor with a lot of options (i.e. image cropper). The view partial renders the assigned picture, resizes it and crops it if needed. ::: tip INFO See the [rendering images](render_images) guide for further information on the powerful image rendering engine of Alchemy. ::: ### Settings * **size** `String` The size the image should be downsized to. I.e. 400x300 * **crop** `Boolean` (default: false) Crop the image to given size. This also enables the build in cropper tool. * **srcset** `Array<String>` A list of sizes of this image uses as sources list. Best used with the `sizes` setting for implementing responsive images. * **sizes** `Array<String>` A list of screen sizes the image sources should be used for. Best used with the `srcset` setting for implementing responsive images. * **linkable** `Boolean` If set to `false`, the user cannot add a link to the picture. ## Datetime Use this to store a `DateTime` value. Renders a datepicker in the editor partial. The view output is passed through Rails' I18n Library, so it is fully localizable. ### Settings * **date_format** `String|Symbol` Either a `String` with the format of [Rubys `strftime`](https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/Date.html#method-i-strftime) or a `Symbol` of a [date/time format](https://guides.rubyonrails.org/i18n.html#adding-date-time-formats) defined in your locale files. ## Html Useful to store HTML code (i.e. a embed, or tracking code). ::: warning The view renders the raw, not sanitized or escaped output. **So be careful!** ::: ## Boolean Stores a `Boolean` value in the database. Renders a checkbox in the editor partial. ## Select Renders a select box in the editor partial and stores the value as `String`. Useful for letting your user select from a limited set of choices. ### Settings * **select_values** `Array` A list of values your users can select from. Use [a two dimensional array](https://guides.rubyonrails.org/form_helpers.html#the-select-and-option-tags) for having value and text pairs. ### Example ~~~ yaml - name: width type: Select settings: select_values: ['200', '300', '400'] ~~~ ::: tip If you need dynamic values (ie, a from a product catalogue), please [create a custom ingredient class](how_to_create_custom_ingredients) that provides the values. ::: ## Link Stores a url in the database. Useful for linking things, where the editor should not set the linked text itself. ::: tip If you want the linked text to be editable by the editor use the [**Text**](#text) with `linkable: true` option instead. ::: ## Number Stores a number in the database. Useful for slider durations, price, lengths, etc. The number value gets translated via Rails' [`number_to_human`](https://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_human) helper. ### Settings * **input_type** `String|Symbol` The input type to render the editor with. Defaults to `number`. Can be set to `range` instead. * **step** `Integer|Decimal` The step on the editor input. * **min** `Integer|Decimal` The minimum number allowed on the editor input. * **max** `Integer|Decimal` The maximum number allowed on the editor input. * **unit** `String` The unit displayed on the editor input addon. Also used as `unit` for the `number_to_human` helper. ## Page References an [Alchemy::Page](pages). Renders a select box in the editor partial. Useful for contact form follow up pages or other use cases where you want to reference another page. ### Example ~~~ yaml - name: contact_form ingredients: - role: follow_up_page type: Page ~~~ ## Rendering ingredients within your element Similar to [rendering elements within layouts](elements.html#render_elements_in_your_layout), ingredients are rendered on an element. Page layouts use the `<%= render_elements %>` helper to load an elements partial, elements use the `<%= element_view_for %>` helper to expose a block that makes it easy to access the ingredients (instances of `Alchemy::Ingredient` subclasses) of the element. For example. With an `article` element like this: ~~~ yaml # config/alchemy/elements.yml - name: article ingredients: - role: headline type: Text - role: color type: Text settings: input_type: color - role: copy type: Richtext ~~~ The `_article.html.erb` template generated by `rails alchemy:elements --skip` would look like this: ~~~erb <%- cache(article) do -%> <%= element_view_for(article) do |el| -%> <div class="headline"> <%= el.render :headline %> </div> <div class="color"> <%= el.render :color %> </div> <div class="copy"> <%= el.render :copy %> </div> <%- end -%> <%- end -%> ~~~ Note how `element_view_for` allows you to call `el.render` on ingredients within the block. ::: tip `element_view_for` wraps the block in a `<div>` with a set ID and class by default, but this is [customizeable](https://github.com/AlchemyCMS/alchemy_cms/blob/main/app/helpers/alchemy/elements_block_helper.rb#L137) if you pass in arguments: `<%= element_view_for(article, tag: :span, id: 'custom_id', class: 'custom_class') do |el| -%>` ::: Without using the `element_view_for` helper, you can still access ingredients: `article.ingredient_by_role('headline')&.value` But the `el.render` helper takes care of generating the appropriate DOM elements to display the ingredient based on its type. It is recommended you rely on these helpers unless you are comfortable with the structure of the ingredient model you are trying to render. Just like `element_view_for`, you can pass `options` and `html_options` to `el.render`: ~~~erb <%= el.render :headline, options = {}, html_options = {} %> ~~~ You can check out the [`ElementViewHelper#render` helper](https://github.com/AlchemyCMS/alchemy_cms/blob/b8cc62493693a070f7457081760708bf28c13e34/app/helpers/alchemy/elements_block_helper.rb#L31) for details. ::: warning NOTE Options are not universally applied by all element types. For example, `ElementRichText` ignores `html_options` since it's already in HTML format and isn't wrapped in any special div. And you can't prevent the `ElementPicture` from generating an `<img>` tag. ::: ### Options The `options = {}` hash is used for formatting the child elements' tags. You can pass `false` to not include tags inside the wrapper element. For more, see the source code [here](https://github.com/AlchemyCMS/alchemy_cms/blob/56e95a07446fb6832676acfc1e8c7047d1b80495/app/helpers/alchemy/elements_block_helper.rb#L128) and [here](https://github.com/AlchemyCMS/alchemy_cms/blob/main/app/helpers/alchemy/elements_helper.rb#L193) ### Html_options The `html_option = {}` are: * `:tag` - The HTML tag to be used for the wrapping element * `:id` - The wrapper tag's DOM ID. * `:class` - The wrapper tag's DOM class. --- URL: "/v7/pages.html" LLMS_URL: "/v7/pages.md" prev: "about" next: "elements" --- # Pages ## Overview In contrary to most CMSs pages in Alchemy **do not hold the actual content**. The actual content is stored in [ingredients](ingredients) inside of [elements](elements) pages are built of. Pages have a unique friendly url and are organized as nested tree and represent the structure of your website. Beside a language, a page has attributes for name, title, visibility, published and restriction status and all SEO relevant attributes like meta tags and meta descriptions. Every page has a [definition](#defining-page-layouts) which defines additional properties like caching, uniqueness and it defines [elements](elements) that can be placed on that Page. ## Defining page types Page types are defined in the `config/alchemy/page_layouts.yml` file. Every page type needs at least a name. You don't need to set every option. It depends on what you need for pages with that type. ### Recommended settings * **name** `String` *required* The name of the page type used for views and inside the database. You can render a page by type with the `render_page_layout(name)` helper. * **elements** `Array` A list of element names that can be placed on this type of page i.e. `[text, picture]`. [Elements](elements) are defined inside the `elements.yml` file. * **autogenerate** `Array` A list of element names that are autogenerated after creating a Page of that type. * **unique** `Boolean` (Default `false`) Pass `true` and the user can only choose this page type once inside a language tree. * **cache** `Boolean` (Default `true`) Pass `false` to disable the cache headers for this kind of pages. **Recommended for contact forms** and such likes. * **layoutpage** `Boolean` (Default `false`) Layout pages (or [global pages](#global-pages)) are outside the normal page tree and can be used to place "global" Elements like a header and footer. * **taggable** `Boolean` (Default `false`) Pass `true` to be able to assign tags within page settings. ### Optional settings * **hide** `Boolean` (Default `false`) Pass `true` to hide this page type from the user. * **searchresults** `Boolean` (Default `false`) Pass `true` to use this type of page for rendering the search results of the build in fulltext search. * **feed** `Boolean` (Default `false`) Pass `true` to enable a RSS feed of news elements from this page. * **controller** `String` Controller to use instead of the default `Alchemy::PagesController` * **action** `String` Controllers action to use instead of the default `Alchemy::PagesController#show` ### Example Lets say you want to create a contact page with a headline element, a contactform and a text element on it. This page should be unique, because you don't want to give your content manager the possibility to create more than one contact form. This page must not be cached, because of validation messages and user specific form content. We also want to autogenerate the headline and the contactform element after the page gets created. ~~~ yaml # config/alchemy/page_layouts.yml - name: contact cache: false unique: true elements: [headline, contactform, text] autogenerate: [headline, contactform] ~~~ ::: warning NOTE Please ensure to restart the Rails server after editing `page_layouts.yml`. ::: ## Page templates Each page type (called `page_layout` in Alchemy) has a Rails view partial which is yielded on the Rails application layout (`app/views/layouts/application.html.erb`). All page type partials live in the `app/views/alchemy/page_layouts` folder. They are named after the pages `page_layout` you defined in the `page_layouts.yml` file. ::: warning NOTE If no page type partial is found for a page, the `_standard.html.erb` partial gets rendered instead. ::: ### Template generator There is no need to create these partials manually. AlchemyCMS comes with a Rails generator task which creates these partials for you. So after defining the page layouts, you can generate all the corresponding partials for them. ~~~ bash bin/rails g alchemy:page_layouts --skip ~~~ Using the example above, which defines a contact page type, the generator will create a partial named `_contact.html.erb`. ::: tip You can pass `--template-engine` or `-e` as option to use one of `haml`, `slim` and `erb`. The default depends on your default template engine in your Rails host app. ::: ### Rendering elements Alchemy does not place any HTML markup in your generated page layouts partial. So: ~~~ erb <%= render_elements %> ~~~ is all you will see. Feel free to customize the HTML so it fits your needs. ::: tip The `render_elements` view helper has lots of options. Please have a look at the [`Alchemy::ElementsHelper` documentation](https://www.rubydoc.info/github/AlchemyCMS/alchemy_cms/Alchemy/ElementsHelper#render_elements-instance_method) ::: ## Global pages Global pages (or layout pages) are pages that are not in the default page tree (your navigation). They will never get rendered on its own. Use them to store shared elements that should be rendered on multiple other pages (ie. footer, header, tracking codes etc) or somewhere directly on the application layout. To define a global page set `layoutpage: true` in the page type definition of that page. ### Render an element from a global page To render an element from a global page use the `from_page` option of the `render_elements` helper. #### Example ~~~ erb <%= render_elements only: 'news_teaser', from_page: Alchemy::Page.find_by(name: 'sidebar') %> ~~~ ::: tip You can pass a `page_layout` name as a `String`, an `Array` of page type names, or an instance of a certain `Alchemy::Page`. ::: ## Caching If using the global caching option (defined in `config/alchemy/config.yml`) - which is enabled by default - your page requests will deliver cache headers. Most browsers, CDNs and proxys use these headers to cache the page. ::: tip You can disable cache headers for certain pages by using the `cache: false` setting. ::: ### Russian doll caching You can use Rails' "Russian-Doll-Caching" to cache page templates. ```erb <% cache @page do %> <%= render_elements %> <% end %> ``` ::: warning Be sure to not cache page templates that have elements with forms on it, like contact or comment forms. Rails' csrf protection token is placed inside the `<form`> tag and caching it will break form submissions. ::: ## Translate page type names Page type names are passed through the `I18n` library. You can translate them in your Alchemy locale files. ### Example ~~~ yaml # config/locales/alchemy.de.yml de: alchemy: page_layout_names: contact: Kontakt search: Suche ~~~ ::: tip All translation keys used by Alchemy are scoped under the `alchemy` namespace. ::: --- URL: "/v7/render_images.html" LLMS_URL: "/v7/render_images.md" prev: "essences" next: "configuration" --- # Render Images Alchemy uses [Dragonfly](https://markevans.github.io/dragonfly/) to render images. Images are stored as master images in the pictury library. The editor just assigns these master images to content elements you provide. You also set the image rendering bounderies. The max width and height values, or even the size an image should be cropped to. Alchemy even comes with a built in image cropping tool, so that your editors can define the mask to be used. ## Configuration * **output_image_jpg_quality** `Integer` (Default `85`) If image gets rendered as JPG this is the quality setting for it. * **preprocess_image_resize** `String` (Default `nil`) Example `1000x1000`. If you are limited on diskspace use this option to downsize large images after upload. * **image_output_format** `String` (Default `jpg`) The global image output format setting. ::: tip You can always override the output format while rendering your essence with passing `{format: 'png'}` to the `el.render(:image)` method. ::: ### The default configuration ~~~ yaml output_image_jpg_quality: 85 preprocess_image_resize: image_output_format: jpg ~~~ ## Rendering In most cases the `el.render(:image)` method is the perfect fit for rendering images. It handles all advanced stuff for you (Loading the image instance, setting all needed options and sets the correct security token). ### Rendering Options * **size** `String` The dimensions the image should be resized to, while keeping the aspect ratio. Example `"400x300"`. * **crop** `Boolean` Pass `true` to crop the image to the given size. * **format** `Symbol|String` The output format of the image. `:jpg`, `:gif` or `:png`. * **quality** `Integer` The quality of the rendered jpg image. Obviously only used on jpg images. ### Advanced rendering If you want to render an image on your own, please have a look at the `show_alchemy_picture_url` helper in the [Alchemy documentation](https://www.rubydoc.info/github/AlchemyCMS/alchemy_cms/Alchemy/UrlHelper#show_alchemy_picture_path-instance_method). ## Caching In production environments you want to implement a cache to avoid rendering the same image over and over again. All supported rendering options are reflected in the url schema. Even the quality of a rendered jpg image and all cropping options. So adding a CDN or HTTP cache (ie. `Rack::Cache`) is the recommended caching option. ::: tip Please be sure to read the [Dragonfly caching guide](https://markevans.github.io/dragonfly/cache) for further informations. ::: --- URL: "/v7/upgrading.html" LLMS_URL: "/v7/upgrading.md" prev: "configuration" next: false --- # Updating AlchemyCMS This guide describes how to update AlchemyCMS to a new version. Updating Alchemy is mostly three steps. 1. [Update the gem](#update-the-gem) 2. [Run the upgrade task](#run-the-upgrade-task) 3. [Follow ups](#follow-ups) ## Update the gem If you use Alchemy from a git branch, point it to the next stable release. ~~~diff ... -gem 'alchemy_cms', github: 'AlchemyCMS/alchemy_cms', branch: '5.2-stable' +gem 'alchemy_cms', github: 'AlchemyCMS/alchemy_cms', branch: '5.3-stable' ... ~~~ If you use Alchemy from rubygems, point it to the next stable release. ~~~diff ... -gem 'alchemy_cms', '~> 5.2' +gem 'alchemy_cms', '~> 5.3' ... ~~~ If you are using `alchemy-devise` for authentication, then update the gem as well. The major and minor versions of the `alchemy_cms` and the `alchemy-devise` gems match. ~~~diff ... -gem 'alchemy_cms', '~> 5.2' +gem 'alchemy_cms', '~> 5.3' -gem 'alchemy-devise', '~> 5.2' +gem 'alchemy-devise', '~> 5.3' ... ~~~ Now update via bundler ~~~bash bundle update alchemy_cms alchemy-devise ~~~ ## Run the upgrade task Now you can run the upgrade task. While upgrading, you will get informations about the process on your screen. ~~~bash bin/rake alchemy:upgrade ~~~ and follow the on screen instructions. ### Update the default config If new configuration options have been introduced you see them in the `config/alchemy/config.yml.defaults` file. Simply copy them over and have a look at the `git diff`. Keep your changes remove or add new keys as necessary. If you have also `alchemy-devise` installed you need to . ~~~bash bin/rails g alchemy_devise:install ~~~ ### Run single upgrade tasks Upgrade tasks can also be run on its own. For example to run only the `alchemy:upgrade:config` task: ~~~bash bin/rake alchemy:upgrade:config ~~~ A list of all upgrade tasks can be listed with ~~~bash bin/rake -T alchemy:upgrade ~~~ ## Follow ups Most of the time the upgrader does all the work for you. But sometimes the upgrade needs some manual work. This will be noted as TODOs at the end of the upgrade task. Please follow them carefully. ## Major version upgrades Major version upgrades follow the same upgrade process as minor versions, but they will have breaking changes. Major versions remove deprecated features and old upgrade tasks. Please make sure you addressed all deprecations and run all upgrades in the current version before upgrading to the next major version. ## Finally Please always verify the upgrade by looking through the `git diff` and by running your test suite for customization you made. Also the [CHANGELOG](https://github.com/AlchemyCMS/alchemy_cms/blob/main/CHANGELOG.md) is a good place to look for changes.