Skip to main content
Version: 0.7

Marten 0.7.0 release notes

August 29, 2026.

Requirements and compatibility

  • Crystal: 1.17, 1.18, 1.19, 1.20, and 1.21.
  • Databases:
    • MariaDB 10.11 and higher.
    • MySQL 8.4 and higher.
    • PostgreSQL 15 and higher.
    • SQLite 3.37.0 and higher.

New features

Polymorphic relationships

Polymorphic relationships can now be defined through the use of polymorphic fields. Those are useful when you want to store a reference to a record whose model can vary among a predefined set of possible types.

For example:

class Article < Marten::Model
field :id, :big_int, primary_key: true, auto: true
field :title, :string, max_size: 128
end

class Recipe < Marten::Model
field :id, :big_int, primary_key: true, auto: true
field :title, :string, max_size: 128
end

class Comment < Marten::Model
field :id, :big_int, primary_key: true, auto: true
field :target, :polymorphic, to: [Article, Recipe], related: :comments, on_delete: :cascade
field :text, :text
end

In the above example, a Comment record could be associated with an Article or a Recipe record, and each of these models could have many associated Comment records. The on_delete option can be used to configure how related records are handled when a targeted record is deleted, like for other relation fields.

Helper methods and convenient scopes are also generated for polymorphic fields, allowing to interact with the related records in a type-safe manner. For example:

# Create an article
article = Article.create!(title: "This is an article")

# Create a recipe
recipe = Recipe.create!(title: "This is a recipe")

# Create a comment
comment = Comment.create!(text: "This is a comment", target: article)

# Regular getter methods
comment.target # => #<Article:0x1036e3ee0 id: 1, title: "This is an article">
comment.target_type # => "Article"
comment.target_id # => 1

# Type class getter method
comment.target_class # => Article (or nil if no related record is set)
comment.target_class! # => Article (or raise if no related record is set)

# Predicate helper methods
comment.article_target? # => true
comment.recipe_target? # => false

# Typed getters methods
comment.article_target # => Returns the associated Recipe record if the targeted record is indeed a Recipe record (or nil otherwise)
comment.article_target! # => Returns the associated Recipe record if the targeted record is indeed a Recipe record (or raise otherwise)

# Type-specific model scopes (generated based on the specified type classes)
Comment.with_article_target # => Returns all the comments associated with Article records
Comment.with_recipe_target # => Returns all the comments associated with Recipe records

Please refer to Polymorphic relationships to learn more about this new capability.

Email attachments

Marten emails can now include attachments through a dedicated #attach method, making it easy to attach files from disk, existing File objects, or arbitrary IOs.

For example:

class WelcomeEmail < Marten::Email
to @user.email
subject "Hello!"
template_name "emails/welcome_email.html"

before_deliver :add_attachments

def initialize(@user : User)
end

private def add_attachments
# Attach a file from a path.
attach "public/docs/terms.pdf"

# Attach generated content from an IO.
attach generate_pdf_io(@user), filename: "welcome.pdf", mime_type: "application/pdf"
end
end

Please refer to the Defining attachments section to learn more about this feature.

Field transformations

Query sets can now filter on date and date/time components through field transformations. These lookups apply the extraction at the SQL level (for example with strftime on SQLite or EXTRACT on PostgreSQL) instead of loading and comparing values in Crystal.

The following transformations are available:

  • year, month, and day — for date_time and date fields
  • hour, minute, and second — for date_time fields only

They can be combined with the usual field predicates. For example:

Article.filter(released_on__year: 2022)
Article.filter(created_at__year__gte: 2022)
Article.filter(created_at__hour__lt: 12)
Article.filter(created_at__month__in: [11, 12])
Article.filter(created_at__year__isnull: false)

Please refer to the field transformations reference for the full list of supported lookups.

Decimal fields

It is now possible to define decimal model fields in order to persist fixed-precision decimal numbers. These fields map to BigDecimal values in Crystal and are the right choice for monetary amounts and other values that must not lose precision (unlike float fields).

Both max_digits and decimal_places are required:

class Product < Marten::Model
field :id, :big_int, primary_key: true, auto: true
field :price, :decimal, max_digits: 10, decimal_places: 2
end

product = Product.create!(price: BigDecimal.new("19.99"))
product.price # => 19.99

# Float, Int, and String values are also accepted
product.price = 19.99

decimal schema fields are also available for validating incoming form and API data as BigDecimal values, using the same max_digits and decimal_places constraints:

class ProductSchema < Marten::Schema
field :price, :decimal, max_digits: 10, decimal_places: 2
end

Please refer to the model fields reference and the schema fields reference to learn more about decimal fields.

Template whitespace control

The Marten templating language now supports whitespace control through the use of hyphens in tag, variable, and comment delimiters. Adding a hyphen (-) immediately after an opening delimiter or immediately before a closing delimiter allows to strip adjacent whitespace (spaces, tabs, and newlines) from the rendered output.

For example:

{%- if username == "John Doe" -%}
Wow, {{ username -}} , you have a long name!
{%- else -%}
Hello there!
{%- endif %}

The above template would render Wow, John Doe, you have a long name! when username is set to John Doe, and Hello there! otherwise — without the indentation and extra newlines from the template source.

Please refer to the Whitespace control section for more information.

Minor features

Models and databases

  • Query sets now provide a #limit method and an #offset method. Those methods respectively allow to limit the number of records returned and to offset the records returned. They act as aliases for the #[] method with a range.
  • Indexes can now be created and dropped without locking the table for writes by passing concurrently: true to add_index and remove_index. Migrations that use this option must set atomic false.
  • The #update query set method can now be called on model classes directly, thus allowing to update all the records of the considered model with the specified values.
  • It is now possible to update only specific columns without running validations or callbacks for a model record by using the #update_columns method.
  • It is now possible to easily create or update records in a single call by using the #update_or_create query set method. An #update_or_create! variant is also available for the same purpose, but it raises in case updated/created records are invalid.
  • Query sets can now be updated using raw SQL expressions through the #update method, allowing to perform SQL-level updates (eg. copying one column into another).
  • Query sets can now be filtered with enum values directly.
  • string fields now support a choices option allowing to restrict field values to a predefined set of strings.
  • Improve thread-safety of database connection transaction tracking and database connection pool initialization, making it safer to run Marten applications with Crystal's multi-threading support.

Handlers and HTTP

Templates

  • Most informational string properties (such as #ascii_only?, #blank?, #bytesize, #empty?, #size, and #valid_encoding?) are now available as template attributes for string values and safe string values.
  • A filter template tag was introduced, allowing to apply one or more filters to the content of a template block.
  • A truncate filter was added, allowing to truncate a string so that it fits within a maximum number of characters.
  • Improve thread-safety of the cached template loader, making it safer to run Marten applications with Crystal's multi-threading support.

Schemas

  • string fields now support a choices option allowing to restrict field values to a predefined set of strings.

Development

  • The flash store can now be accessed from the spec client by using the #flash method. See Flash messages for more details.
  • The ability to bind the server to a Unix socket was introduced by adding a new socket setting.
  • A new main_app_label setting was introduced, allowing to configure the label of the main application.
  • The migrate management command now accepts a --check option that exits with a non-zero status when unapplied migrations exist, without applying them.
  • The migrate management command now accepts a --prune option that deletes nonexistent migrations from the marten_migrations table.
  • The genmigrations management command now accepts a --check option that exits with a non-zero status when model changes require migrations, without generating migration files.
  • The genmigrations management command now accepts a --dry-run option that shows migrations that would be generated without writing them.
  • The genmigrations management command now accepts a --no-header option that does not add header comments at the top of newly generated migration files.
  • A new collectartifacts management command was introduced, allowing to collect runtime artifacts (eg. locales and templates) into a deployable directory that can be used with the root_path setting in production (see Collecting runtime artifacts).
  • The resetmigrations management command now accepts a --no-header option that does not add header comments at the top of newly generated migration files.
  • The routes management command now accepts a --grep option that allows to only display routes whose path or name contains the given substring (case-insensitive).
  • A new parallelism setting was introduced, allowing to configure the maximum parallelism of Crystal's default execution context when starting the HTTP server (Crystal 1.21+). The initial value can also be provided via the MARTEN_PARALLELISM environment variable.
  • Projects generated with the new management command now include the X-Content-Type-Options middleware and the Cross-Origin-Opener-Policy middleware by default.

Caching

  • Improve thread-safety of the Marten::Cache::Store::Memory cache store, making it safer to run Marten applications with Crystal's multi-threading support.

Backward incompatible changes

Handlers and HTTP

  • Incoming request bodies are now limited to 2.5MB by default through the request_max_body_size setting. Requests exceeding this limit result in a Marten::HTTP::Errors::RequestBodyTooBig exception and a 400 Bad Request response. Applications that need to accept larger uploads (such as files) should increase this setting accordingly. This protection can also be disabled by setting request_max_body_size to nil.