Skip to content

Build a service page with products and a Typeform

Service pages (/services/...) use three body templates built for this site. They decide what a page shows from fields that stock Maropost treats as plain labels: Label 1 turns on a product price list and becomes its heading, and Label 3 holds a Typeform embed ID. The products in that list are not the products assigned to the page in the usual way. They come from a separate product feed and are matched to the page by its Content ID.

Use this page when you create a service page, add a price list or booking form to one, or switch a page to a different service template. For image sizes and copy guidelines, see Services navigation. Where the two pages disagree, this one was checked against the theme and the live site on 21 October 2026.

Pick the template

Set the template in the page’s Body Template field.

TemplateUse forChild cardsProduct price list (Label 1)Typeform (Label 3)Fixed “Why Choose Altapac?” section
service-parentA top-level service with child service pages, such as Window TintingYesNoNoNo
servicesA single service page with no children. Also used by the location pages (/locations/kotara, /locations/artarmon/towbars and so on)NoYesYesNo
serviceNewThe same as services, plus a fixed company section. Live on one page only: Roof Rack InstallationNoYesYesYes

All three share the same top of page: the main image as a banner, the page heading over it, the Short Description (ShortDescription1) under the heading, then Description 1 on the left and Alt image 1 (alt_1) on the right.

Every service child page except Roof Rack Installation has Body Template left blank (30 of 31 in the 2026-10-20 pull), and a blank template gives the services layout. Setting services explicitly on new pages gives the same layout without relying on the store’s default.

Build a service parent page

  1. Create a content page with Content Type set to the Services type. All live service parents are top level (no parent).
  2. Set Body Template to service-parent.
  3. Fill in Name, Short Description, the main image, Description 1 and Alt image 1. The page heading is the SEO page heading if you’ve set one, otherwise the Name.
  4. Description 2 is optional. It appears at the bottom of the page.
  5. Leave Label 1 and Label 3 empty. This template has no price list and no Typeform slot, so nothing you put there will show as a list or a form.

Child cards

The parent builds one card for each child page. A child gets a card only if all of these are true:

  • its parent is this page (direct children only, so grandchildren don’t get a card)
  • its content type is the Services type
  • it is active
  • it is set to show on the menu. On the live site, the children with this switched off (Roof Boxes, UHF Comms Installation, the service page template, Automotive Window Tinting in Maitland) get no card on their parent.

Each card shows the child’s Alt image 1, its Name and its Short Description, and links to the child page. Cards are three across on desktop.

Upload Alt image 1 at 450 × 450 (square), the size the site uses for it everywhere. The card image fills the card width and is cropped to 200px high. On a desktop screen that’s about 340 × 200, a little wider than 3:2. The same image shows at 3:2 beside Description 1 on the child page. Both crops keep the middle of the square and cut the top and bottom, so keep the subject in the centre, filling the frame.

Cards are ordered by Sort Order, highest first, then by name A to Z. On Vehicle Protection, Paint Protection Film has Sort Order 1 and comes first; the rest are 0 and sort alphabetically.

If a parent has no child that meets the rules above, the card section doesn’t appear at all. Signage & Graphics is like this today: all four of its children are inactive.

Build a service page

  1. Create a content page with Content Type set to the Services type, and set its parent to the service parent it belongs under.
  2. Set Body Template to services. Use serviceNew only if you want the fixed company section (see The serviceNew company section).
  3. Fill in Name, Short Description, the main image, Description 1 and Alt image 1. The Short Description and Alt image 1 are also what the parent page’s card shows.
  4. Fill in Description 2 if you want a booking form or extra content at the bottom. The Typeform slot only exists inside the Description 2 section.
  5. To add a price list, follow Add a product price list.
  6. To add a booking form, follow Add a Typeform.

Add a product price list

The price list is a row of cards, one per product. Each card shows the product’s image, name, price and a short description, with a Call Us button that rings the store’s main phone number. The cards don’t link to the product page. When you hover over a card, it swaps to the product’s second image.

The cards are built in the visitor’s browser from a public product feed at https://www.altapac.com/export//service-product.json. The page shows only the products whose category value in that feed includes the page’s Content ID. Products assigned to the page in the usual Maropost way don’t show unless the feed also carries the page’s ID.

  1. Find the page’s Content ID. For example, Automotive Window Tinting is 106.
  2. Tag each product with that ID so it appears in the feed with it (see the note below). If one product belongs on several service pages, the feed’s category can hold several IDs separated by commas, and the product shows on each of those pages.
  3. Check the feed. Open https://www.altapac.com/export//service-product.json in a browser and search for the product name. Its entry must show "category": "106" (your page’s ID). If the product isn’t in the feed, or its category is "", it won’t show on any page.
  4. Check the product’s images. Give each product a main image and a second image. A product with no second image shows a blank card when someone hovers over it.
  5. Set Label 1 on the service page to the heading you want above the cards, for example Window tint prices. Any text turns the list on, in any case. The text you type is also the heading shown on the page. An empty Label 1 means no list, whatever is in the feed.
  6. Save the page and check it on the live site.

The heading and cards stay hidden until at least one card loads, so a page whose Content ID isn’t in the feed shows nothing at all. The theme also copes with the doubled quote marks the feed puts in some product names (Fitment of ""J"" Brace).

The card description is cut to the first 100 characters, and ... is always added to the end, even when the description is shorter. “Match Privacy Glass” shows as “Match Privacy Glass…“. Products appear in the order they appear in the feed.

Judging by their URLs, the two card images are the product’s main image and its first alternative image.

Add a Typeform

The services and serviceNew templates can place a Typeform at the top of the Description 2 section, above the Description 2 content.

  1. In Typeform, open the form’s embed code for a web page. Copy the value of data-tf-live. It is a 26-character ID, such as 01HCVDYRQ0SSKNZ1W1V2KM0EW0.
  2. Paste that ID, and nothing else, into the service page’s Label 3.
  3. Make sure Description 2 has content. If Description 2 is empty, the whole section is skipped and the form doesn’t appear, even with Label 3 set.
  4. Remove any Typeform you pasted into Description 2 by hand, or the page shows two forms.
  5. Save and check the page.

On service-parent pages, Label 3 does nothing. A form on a parent page has to be pasted into Description 2 as embed code, as Window Tinting does today.

The serviceNew company section

serviceNew adds a fixed “Why Choose Altapac?” section: four figures (since 1972, 4 NSW workshops, 30,000+ fitments, 4.6 stars from 1113 Google reviews), four selling points with a workshop photo, three customer reviews (Darren M., James C., Christopher Y.) and “More about us” links.

  • Its text, figures and reviews are written into the theme. You can’t edit them in cPanel. To change the review count or a quote, ask the developer.
  • It appears only when Description 2 or Description 3 has content.
  • It sits between Description 2 and Description 3. Description 3 is shown at the very bottom, and only on serviceNew.
  • The theme styles the section itself, so it looks the same on any page you switch to serviceNew.

Live state (21 October 2026)

  • No service page shows a price list. None of the pages using services or serviceNew has Label 1 set. The feed holds 99 products; only 4 have a category, all 106 (Automotive Window Tinting: Coupe, CUV, Dual Cab and Front Only tints). The developer backlog plans to set Label 1 on page 106 once the product list fix (AUD-36) is live.
  • No page uses Label 3. Every Typeform on a service page today (Window Tinting, Automotive, Commercial and Residential Window Tinting, Paint Protection Film, Roof Rack Installation and others) is embed code pasted into Description 2.

What breaks quietly

You did thisWhat happens
Set Label 1 but no product in the feed carries the page’s Content IDNo price list and no heading
Tagged the product but didn’t set Label 1No price list
Set Label 1 on altapac.com todayHeading over an empty box, because the live theme can’t read the feed (not live yet, AUD-36)
Changed product assignment on the page in the usual Maropost wayNothing. The list only reads the feed’s category value
Product has no second imageThe card goes blank on hover
Set Label 3 with Description 2 emptyNo form
Put the Typeform share-link ID in Label 3An empty box that never loads
Set Label 3 on a page that also has a pasted Typeform in Description 2Two forms
Set Label 3 on a service-parent pageNothing
Child page has show-on-menu off, is inactive, or is a different content typeNo card on its parent
Child page’s Alt image 1 has its subject near the top or bottomSubject cut off on the card and on its own page, which both crop the square to a wide shape
Switched a page with its own “Why Choose” copy to serviceNewThe section shows twice
Left Description 2 and Description 3 empty on serviceNewNo “Why Choose” section

Field reference

Fieldservice-parentservices and serviceNew
Main imageBannerBanner
Short DescriptionUnder the headingUnder the heading, and on the parent’s card
Description 1Left of Alt image 1Left of Alt image 1
Alt image 1 (450 × 450)Beside Description 1 (3:2)Beside Description 1 (3:2), and the parent’s card image
Description 2Bottom sectionBottom section; needed for the Typeform
Description 3Not usedserviceNew only: very bottom
Label 1Heading above the child cards, in the theme source; untested, so leave it empty (not live yet, AUD-87)Turns on the price list; becomes its heading
Label 3Not usedTypeform embed ID (data-tf-live)
Sort OrderOrders child cards on the parent, highest first, then nameOrders this page’s card on its parent

For developers

Theme files and evidence this page is based on

From Altapac-ReDev, branch development:

  • src/templates/cms/service-parent.template.html: child cards via content_menu content_type:'services' sortby:'sortorder, name' show_empty:'1' category:'[@content_id@]' start_category:'[@content_id@]', *level_1 only. Card image asset_url ... thumb:'alt_1' with width="450px" height="300px"; CSS #service-children .card-img-top { height: 200px; object-fit: cover }. Heading guarded with [%set [@children_heading@]%][@content_label1@][%/set%] (commit 406e84c, AUD-87). No Label 3 handling.
  • src/templates/cms/services.template.html: product list lines 137-227 inside [%if [@content_label1@]%]. Fetches /export//service-product.json, repairs CSV-doubled quotes, strips trailing commas, filters on String(product.category).split(',').map(s => s.trim()).includes(String(targetCategoryId)), unhides the wrapper only when a card renders (commit c4bfdae, AUD-36). Description truncated with substring(0, 100) + '...'. Call Us is tel:[@CONFIG:COMPANY_PHONE@]. Typeform lines 229-239: [%if [@content_label3@]%]<div data-tf-live="[@content_label3@]"> with //embed.typeform.com/next/embed.js, nested inside [%if [@content_description2@]%].
  • src/templates/cms/serviceNew.template.html: same product list (202-292) and Typeform (294-306); Description 2 is wrapped in [%parse%], so B@SE tags in it are evaluated. “Why Choose Altapac?” section 308-404 inside [%if [@content_description3@] || [@content_description2@]%], styled by .ap-mod rules at 106-161 (commit b987e8f, AUD-86). Description 3 at 407-411 reuses id="_Custom-page-content", a duplicate id on the page.
  • docs/records/2026-09-25-repo-audit/backlog.md: AUD-36, AUD-86 and AUD-87 owner steps. Those three commits are on development only, not main or staging.

Live checks on 21 October 2026: template use from Altapac-Content pages/**/meta.json; feed parsed with old and new code (old fails at line 249, new gives 99 products and 4 for id 106); every active service and location page scanned for id="service-cards" (none), template-style data-tf-live (none), and <h2 class="display3"></h2> (the four parents). Live roof-rack-installation still carries the pre-AUD-86 #_Why-choose CSS. The data-tf-live test loaded 01HCVDYRQ0SSKNZ1W1V2KM0EW0 and left the form ID PjqK3jB2 stuck at data-tf-loading.

The export template behind service-product.json is in cPanel only. AUD-36 suggests emitting name and description through [%escape%] so the feed is valid JSON and the in-code repair becomes a no-op.