All pages
On this page
Content and schemas
View as MarkdownFrontmatter, attribute types, validation and queries.
Every collection declares the frontmatter it expects. Files that do not match
fail the build with the file name and the problem, instead of surfacing as
nil in a view.
Declaring attributes
module Content
class Post < Andromeda::Entry
collection :posts, base: "app/content/posts", pattern: "**/*.{md,mdx}"
attribute :title, :string, required: true
attribute :pub_date, :date, required: true
attribute :draft, :boolean, default: false
attribute :tags, :array, of: :string, default: []
attribute :hero_image, :image
attribute :author, :reference, collection: :authors
scope :published, -> { where(draft: false) }
def reading_time = (body.length / 400.0).ceil
end
end| Type | Accepts | Becomes |
|---|---|---|
:string :integer :float :boolean | the matching YAML type | itself |
:date :datetime | 2026-09-01, 'Jul 08 2022', ISO strings | Date / Time |
:array | a list, with of: for the element type | Array |
:enum | one of values: | the value |
:hash | a mapping | Hash |
:image | a path relative to the entry | an image, published through the asset pipeline |
:reference | an id in another collection | the target entry, resolved lazily |
Options: required:, default: (a value or a callable), of:, values:,
collection:.
References
A :reference holds the id of an entry in another collection, named by
collection:. For a list of references, use :array with of: :reference
and put collection: on the same attribute:
attribute :author, :reference, collection: :authors
attribute :categories, :array, of: :reference, collection: :categories, default: []author: jane
categories: [exam-prep, abroad]The readers return entries: post.author is the author, and post.categories
the categories in the order written. They are resolved lazily — the target
collection is loaded the first time a reader asks — and an id with no entry
raises Andromeda::EntryNotFound. post.data[:author] and
post.data[:categories] keep the raw Andromeda::Reference pointers
(collection and id).
andromeda:build and andromeda:check look up every reference, so a mistyped
id fails the build instead of the page that shows it.
Frontmatter
YAML (---) and TOML (+++) are both supported. YAML is read with 1.2
semantics, matching Astro: yes and no stay strings, while true and
false are booleans. Dates and timestamps become Date and Time.
Keys are snake_case. Content copied from an Astro project usually has
camelCase keys; bin/rails andromeda:fix rewrites them, and
bin/rails andromeda:check reports them.
Querying
Content::Post.all
Content::Post.published.order(pub_date: :desc).limit(10)
Content::Post.published.order(pub_date: :desc).offset(10).limit(10) # second page of ten
Content::Post.where(draft: false, lang: "en") # every key must match
Content::Post.where { |post| post.tags.include?("rails") }
Content::Post.where { |post| post.data[:categories].any? { |ref| ref.id == "abroad" } }
Content::Post.find("hello-world") # raises Andromeda::EntryNotFoundwhere with a hash compares values for equality, so use the block form to
look inside an array. For an array of references, compare ids through
data: post.categories returns entries, so it never matches a string. order is stable: entries that tie keep the order they
were loaded in, as they do in Astro.
offset(count) skips the first count entries and returns a relation, so a
listing can be paged with offset and limit and still take more scopes.
Entries expose id, data, body, file_path, digest, html and
headings, plus a reader per declared attribute (post.title).
Entry ids
Ids follow Astro’s glob() loader rules, so URLs from an Astro project keep
working:
| File | Id |
|---|---|
hello-world.md | hello-world |
Guides/Getting Started.md | guides/getting-started |
posts/hello/index.mdx | posts/hello |
a slug: in the frontmatter | that value, verbatim |
Last updated September 29, 2026