Skip to content
New: Customize your theme with an AI assistant. Read the guide

Collections

Skill level: Beginner · Files: src/content/<collection>/<language>/, defined in src/content.config.ts

A collection is a folder of similar entries: services, service areas, reviews, job openings, blog posts. Most entries are one file and one page. The list page of the collection (for example /areas/) takes its title and section blocks from the -index.md file in the same folder. Files whose name starts with _ are ignored.

Add An Entry

  1. Copy an existing entry in the folder, for example src/content/areas/english/elm-park.md.
  2. Rename the copy. The file name is the entry’s id and its URL (/areas/lake-view/) unless you set customSlug. Other files refer to entries by this id, for example area: "elm-park" in a review.
  3. Change the frontmatter values and the Markdown body. Keep the keys.
  4. Add the same file in french/ with the translated text and the same file name.

To hide an entry, set draft: true. It still shows in npm run dev, but it is left out of the built site, the lists, and the sitemap. npm run astro-check reports a missing required field or a value of the wrong type in every collection that has a schema.

Every Collection

CollectionFolderPagesSchemaUsed by
servicesservices//services/, /services/<id>/yesServices sections, mega menu, footer, estimator, booking form, included tables
pricingpricing//pricing/yesEvery price on the site: estimator, plans, add-ons, from-prices, booking summary
areasareas//areas/, /areas/<id>/yesAreas section, area finder, booking ZIP check, Areas menu, footer, structured data
reviewsreviews/none (shown on /reviews/ and in sections)yesReviews section in every layout
projectsprojects//projects/, /projects/<id>/yesBefore/after section, project pages
industriesindustries//industries/, /industries/<id>/yesIndustries strip, industries menu, walkthrough form
careerscareers//careers/, /careers/<id>/yesOpen roles section, apply form, job posting data
teamteam//team/ onlyyes (strict)Team section on /team/, /about/, commercial homepage
blogblog//blog/, /blog/<id>/, category and tag pagesyesBlog list, blog teaser, RSS feed
authorauthor/noneyesAuthor box under posts
homepagehomepage// and the demo homepagesyes (page)The homepages’ named blocks
pagespages//<id>/ (text pages) and the pages with their own routeyes (page)Legal pages, book, how it works, reviews, gift cards, referral, elements
about-usabout-us//about/yes (page)About page blocks
faqfaq//faq/yes (page)The full grouped FAQ list (in its questionsSection block)
guaranteeguarantee//guarantee/yes (page)Guarantee page blocks
contactcontact//contact/no schemaContact page blocks
sectionssections/noneno schemaDefault content of every section

yes (page) means the collection uses the shared page schema: the fields every page accepts plus every named block. astro-check checks those blocks too.

The fields every page accepts (title, description, image, draft, customSlug, SEO fields) work on the entries of every collection except reviews and author, which have their own short schemas. They are listed in Pages. The tables below list what each collection adds. Required fields must be present; all others are optional.

Services

One file per service in src/content/services/english/, plus -index.md for /services/ and the shared settings. Full guide: Services & What’s Included.

FieldTypeWhat it does
title (required)textService name on cards, menus, and the H1
tradetrade idIts group: a column of the Services mega menu and a group on /services/
ordernumberOrder inside its trade (lowest first)
options.layoutdefault | cleaning | repair | commercialThe page template and service hero layout
eyebrow, serviceTypetextSmall label above the title; the schema.org serviceType
duration, crew, highlighttextFacts on the cards and in the hero ("4–6 h", "Crew of 2–3")
pricetextText shown instead of the computed from-price, such as "Quote on visit"
image, heroImage, heroImageAltimage pathCard photo; hero photo (empty uses image)
iconLucide nameNot used in this version (the mega menu shows the trade’s icon)
featuredtrue/falseNot used in this version
codetextShort code on the service ticket
relatedservice ids“Often booked with” (empty: the next services of the same trade)
faqlist of question, answerQuestions on the service page (empty hides the FAQ)
includedroom id → task idsTasks done on every visit
extras, notIncludedidsAdd-ons offered, and tasks never done
repair.typical, schedulesrepair template: the typical visit; commercial template: schedule rows
Named blocksAny service page block, for example questionsSection

Only in -index.md: trades (id, label, icon, order, description, image), rooms (the task dictionary every included table reads), detail (blocks shared by every service page), and templates (blocks per template). The price of each service comes from the rate card with the same id in the pricing file.

Pricing

One file, src/content/pricing/english/-index.md: the pricing page and the price data the whole site uses. Fields: rounding, frequencies (id, label, discount 0 to 100, default), rateCards, addOns (id, label, price, services, task, description), jumpNav, ratesSection, and the page blocks. Keep every number identical in the English and French files: npm run build prints a [pricing] warning listing the keys that differ. Full guide: Pricing & Estimator.

Areas

One file per service area. Full guide: Service Areas.

FieldTypeWhat it does
title (required)textArea name in lists, menus, and structured data
zipslist of textPostal codes the area finder and the booking ZIP check match
neighborhoods, aliaseslist of textNames shown on the page and also matched by the finder
ordernumberPosition everywhere (lowest first)
heading, region, codetextH1 (empty uses the default), sub-line in lists, ticket number
image, mapimage pathArea photo; optional static map of your real area
phone, phoneDisplay, hoursLocal contact; each falls back to [business] in config.toml
officestreet, locality, region, postalCodeOnly a real, staffed location; empty keeps the address off the page and out of structured data
crews, sincenumberFacts on the area page
services, serviceNotesServices offered here (empty: all), with notes
introTitle, notes, faq, nearbyArea page copy, questions, and “Also nearby” area ids

Reviews

One file per review, with its own schema (not the page schema). Full guide: Reviews & Projects.

FieldTypeWhat it does
name (required)textFirst name and last initial, such as "Sam K."
service (required)service idThe service reviewed; drives the filter buttons
rating (required)whole number 1 to 5The stars
source (required)source idA rating source id from trust-strip.md (“via …” line)
date (required)dateShown as month and year
text (required)text, up to 700 charactersThe review
areaarea idShown with the name
featuredtrue/falseFeatured band on /reviews/, first pick on the homepage
replytext, nameA reply from the business
drafttrue/falseHides the review

Homio never adds review stars to structured data: reviews hosted on your own site count as self-serving for Google. The sample reviews are invented: replace them with real ones, with the customer’s consent.

Projects

One file per finished job. Fields: ticket (job number), trade, service, area, industry (ids), featured, before, after, beforeAlt, afterAlt, caption, duration (text), crewSize (whole number), scope (check rows), price (amount, note), quote (text, name, detail), and gallery (more before/after pairs). Only in -index.md: relatedProjects. Several sample “before” photos are dulled copies of the “after” photo: replace them with real matched pairs.

Industries

One page per kind of site (offices, clinics, rentals…). Fields: eyebrow (short name), code, weight (order), action (walkthrough or book, default walkthrough), bookService, schedules, services (service ids), serviceNotes, price (service, label, unit), ticket, needsTitle, needs, scopeTitle, notCovered, faq. Only in -index.md: industryField (the form field that lists industries) and detail (blocks shared by every industry page).

Careers

One file per open role:

FieldTypeWhat it does
trade, areaidsGroup heading on /careers/; area id or "all"
typefull-time | part-time | contract | internship | seasonalLabels from careers.types in src/i18n/en.json
paymin, max, unit (hour or year)Pay range, in the currency of [settings.estimator] currency
schedule, starttextFacts in the role ticket
requirementslist of textYes/no checks in the role ticket
applyUrlURLSend applicants to your hiring system or a mailto: instead of the form
date, validThroughdatePosting date and last day to apply
code, weightRole number; order inside its group
applicationIn -index.md: the apply form every role page shows. In a role: enable: false hides it there

Dates, pay, and type feed the JobPosting data search engines read. Keep only real, open roles: replace or delete the samples before launch.

Team

One file per member, plus -index.md for /team/. Fields: title (the name, first name and last initial), profession (role), trade, area, since (year), languages, knownFor (up to 90 characters), image (optional 4:5 portrait), weight. Members have no page of their own, and no personal email, phone, or social links. The schema is strict: a misspelled field fails astro-check.

Blog And Authors

  • Posts are .mdx files in src/content/blog/english/: title, description, image, imageAlt, date, updated, author, categories, tags, featured (the lead post on /blog/), service (a rate-card id: the price band at the end of the post), and draft.
  • Authors live in src/content/author/english/, one file per author: title (required, the name), role, image, description, links. A post’s author: "Tom B." matches tom-b.md.
  • Posts per page: blogPagination in [settings] of config.toml.

See Writing Content for the shortcodes you can use in posts.

Do It With AI

Add a [service | service area | review | project | job opening | team member | blog post] to
src/content/[folder]/english/ for: [DETAILS]. Copy the frontmatter keys of an existing entry in
that folder, keep its comments, and fill in my values only. Check the field types in
src/content.config.ts, and use ids that already exist for trade, service, and area. Then create the
French version in src/content/[folder]/french/ with the same file name, and run npm run astro-check.
Type something to search...