2026March & April tutorial updates are live →

Shopify Liquid Objects: Product, Collection, Cart & Shop

Master the four Liquid objects every Shopify theme developer touches daily: product, collection, cart, and shop. This guide covers properties, dot notation, nil-safe patterns, and real-world usage from ADSPOC's theme builds.

What Liquid objects are and how dot notation works

In Shopify Liquid, objects are structured data containers that the platform injects into your templates at render time. When you write {{ product.title }}, Liquid resolves the product object in the current template context and outputs its title property. Dot notation chains through nested objects: {{ product.featured_image.alt }} walks from the product, into its featured image, and reads the alt text. Arrays use bracket notation: {{ product.images[0].src }} or, more safely, loop with {% for image in product.images %}.

Objects are read-only in theme Liquid—you cannot mutate product.price or cart.item_count from the storefront. What you can do is read properties, branch on them with {% if %}, and pass slices of objects into snippets via the render tag. Understanding which object is in scope on each template type is the foundation of every theme: product pages expose product, collection pages expose collection, cart.liquid exposes cart, and shop is global on every page via layout and section rendering.

The product object: variants, media, and metafields

The product object represents a single catalog item. Core properties include product.title, product.handle, product.description, product.vendor, product.type, and product.tags (an array you can split, join, or check with contains). Pricing lives on variants, not the product root: product.selected_or_first_available_variant.price outputs the active variant price in cents, and you format it with the money filter—{{ product.selected_or_first_available_variant.price | money }}. For variant pickers, iterate {% for variant in product.variants %} and read variant.id, variant.title, variant.available, variant.options, and variant.featured_media.

Media handling shifted from product.images to product.media, which unifies images, videos, and 3D models. Use {% for media in product.media %} and branch on media.media_type. Metafields extend products without code changes: product.metafields.custom.care_instructions.value or product.metafields.reviews.rating.value when defined in Admin. Always nil-check metafields—{% if product.metafields.custom.badge != blank %}—because undefined namespaces return empty drops, not errors, and blank output is better than broken markup.

The collection object: listings, filters, and pagination

On collection templates, collection gives you the current collection's metadata and its products. collection.title, collection.description, collection.handle, and collection.image power hero sections and SEO. The product list is collection.products, paginated by Shopify when you use {% paginate collection.products by 24 %}. Inside the paginate block, paginate.pages, paginate.current_page, and paginate.next.url drive pagination UI. collection.products_count and collection.all_products_count differ when filters narrow results—use products_count for filtered views.

Collection objects also expose sort options and, on Online Store 2.0 themes with filtering enabled, collection.filters for storefront faceted search. Each filter has filter.label, filter.type, and filter.values with value.count and value.active. When building collection grids, pass each product into a snippet: {% render 'product-card', product: product, lazy: true %}. ADSPOC collection templates always lazy-load below-the-fold cards and reserve the first row for LCP-critical images with fetchpriority="high" on the lead product card only.

The cart object: line items, totals, and drawer patterns

cart is available globally and updates after Ajax Cart API calls or full page reloads. cart.item_count is the total quantity across line items; cart.items is the array of line items, each with item.product, item.variant, item.quantity, item.line_price, item.original_line_price, and item.key (required for /cart/change.js updates). cart.total_price, cart.original_total_price, and cart.cart_level_discount_applications power summary rows. Properties added at add-to-cart time appear on item.properties—{% for property in item.properties %}—useful for engraving, gift messages, or bundle identifiers.

Cart drawer themes typically fetch /cart.js as JSON and hydrate Liquid partials via sections rendering, but the cart object still drives server-rendered fallbacks. Check {% if cart.item_count > 0 %} before rendering line items; empty carts should show a CTA back to collections. For free-shipping bars, compare cart.total_price against a threshold stored in theme settings: {% assign remaining = settings.free_shipping_threshold | minus: cart.total_price %}. ADSPOC cart drawers always show line-level compare-at savings and surface item.variant.title when it is not 'Default Title'—small details that reduce support tickets and checkout confusion.

The shop object, nil checks, and defensive Liquid patterns

shop is global: shop.name, shop.url, shop.domain, shop.currency, shop.money_format, shop.enabled_payment_types, and shop.policies (privacy, refund, terms) are available on every page. Use shop.url with routes for locale-aware links: {{ routes.cart_url }}, {{ routes.root_url }}, {{ routes.account_login_url }}. shop.metafields mirrors product metafields at the store level—ideal for announcement banners, global badges, or footer trust copy managed in Admin without redeploying code.

Nil-safe Liquid prevents empty tags and layout collapse. Prefer {% if product != blank %} over assuming context. Use the default filter for fallbacks: {{ product.metafields.custom.subtitle | default: product.vendor }}. For arrays, check size: {% if product.tags.size > 0 %}. The blank keyword treats nil, false, and empty strings as falsy. Avoid chaining deeply without guards—{% if product.featured_media %} {{ product.featured_media.preview_image | image_url: width: 800 }} {% endif %}—because a missing featured media should not break the PDP. These patterns are non-negotiable in ADSPOC production themes where merchants add products with incomplete data daily.

Frequently asked questions

product does not have a reliable root-level price on most templates. Price lives on variants. Use product.selected_or_first_available_variant.price or iterate product.variants. On PDPs with a selected variant from the URL (?variant=ID), selected_or_first_available_variant reflects the picker state. ADSPOC themes always read price from the active variant and update via JavaScript on variant change.

Get a free conversion audit from India's best Shopify builders

ADSPOC since 2000 · India's #1 CRO-focused Shopify agency · any store type · 18-day delivery or money back · 23+ conversion features built in · WhatsApp direct line · trained thousands of developers · Mumbai & Solan, serving India, Bangladesh, Pakistan, and worldwide.

Prefer a quick chat? Message ADSPOC on WhatsApp

Related reading