Collections

Tables of rows shown in pages, members-only rows, webhook feeds and connected apps.

This is the guide the PageHub MCP server gives an AI agent for get_style_reference({ topic: "collections" }). Names like set_theme are its tools (connect one), and the liquid topic is the HTML + Liquid reference.
  • A collection is a table: create_collection({ slug, name, schema }), rows with create_collection_row / create_collection_rows (up to 500, all-or-nothing) / import_collection_csv, edit with update_collection_row, update_collection_schema (schema replaces ALL fields: get_collection first). Field types: text, number, boolean, date, select, image, url, email, color, richText, media, json. Read rows in a page as {% for row in collections.<slug> %} with row.<fieldKey> (richText fields need | raw; an image or media field is a mediaId: {{ row.photo | image_url: width: 800 }}). Filtered, searched and paged lists: the liquid topic, "Lists from the URL". A detail page: update_page({ pathPattern: ":slug" }) and {% assign row = collections.team | where: 'slug', request.params.slug | first %}.
  • Gating (update_collection_schema): requiredPlans: ["pro"] shows the rows only to members of those plans; requiresCustomerLogin: true to any signed-in customer. A page that reads a gated collection is rendered per visitor, not served as a shared file.
  • Per-row lock: add a text field with the key requiredPlans and write comma-separated plan slugs on the rows to lock. A visitor without one of those plans still gets the row, minus the fields named in memberFields (keys of the schema; [] clears), with row.locked true: {% if row.locked %}<a href="/pricing">Join to watch</a>{% else %}{{ row.video }}{% endif %}.
  • Webhook feed (Business and up): set_collection_webhook({ slug }) turns a manual collection into a feed (or pass source: { type: "webhook-feed" } to create_collection). get_collection then shows the ingest URL and how senders sign a POST; rows upsert by externalId. rotate: true mints a new secret (shown once; the old one stops working): hand it to the sender. A collection syncing from Sheets or Airtable can't be converted.
  • Vacation rentals (Hospitable) (Pro and up): connect_app({ app: "hospitable", inputs: { token } }) with the token the owner gives you (or they paste it on the Apps screen) makes the properties collection and syncs; the fields fill in on the first sync. Each row is a home: title, slug, summary, description, picture (a full URL; | image_url: width: 900 passes it through), photos (every photo, { url, thumbnail, caption } in order; the detail block shows them as a gallery with a full-screen viewer and a photo tour, grouped by room when the owner adds a multiline text field photoRooms: one room per line in photo order. Fields the owner adds survive syncs), city, address, latitude, longitude, maxGuests, bedrooms, beds, bathrooms, amenities (list), petsAllowed, checkIn, checkOut, listed, fromPrice (cents) / fromPriceFormatted, nextAvailable, bookingUrl (empty when unset: wrap the Book button in {% if home.bookingUrl %}), and calendar: one entry per night, { date, available, price, priceFormatted, minStay, noCheckin, noCheckout }. Guests pick dates by tapping the detail block's calendar. With checkout on for the collection (set_checkout with fieldMap: { capture: "hold" } to approve each stay) they book and pay on the page, charged the picked nights' prices; otherwise the Book button links to bookingUrl. Pages: block rental-homes-grid on a list page (/stays) and rental-home-detail on a page named "Stay" with pathPattern: ":slug" (cards link to /stay/<slug>). Dates across homes: get_calendar, set_dates (close, reopen, retry a date the rental tool missed), share_calendar_link (the link Airbnb, Vrbo and Booking.com import). Bookings and payments: list_orders / get_order / update_order (see integrations).
  • Other apps fill collections the same way. list_apps shows each one, what it needs and what is connected; connect_app connects one with the key or link the owner gives you (or they use the Apps screen); sync_collection reads it again. Then build pages on the collection. Hostaway and Guesty write the same properties shape as Hospitable; Airbnb, Vrbo and Booking.com calendar links do too, with dates only (the owner adds photos and text). Events (Google, Outlook or Apple calendar links, Eventbrite, Ticket Tailor) go in events: title, start, end, allDay, startDate, endDate, startTime, endTime, timezone, location, description, url, calendar, plus image and ticketUrl from the ticketing tools; block events-upcoming. Blog posts (an RSS feed: Substack, Ghost, WordPress, Medium) go in posts: title, link, date, summary, image, author; block latest-posts. Cal.com open times: title, start, end, startDate, dayLabel, startTime, endTime, timezone, link (books that time), eventType; block next-open-times. Google Sheets, Airtable, Notion, Supabase, Baserow and NocoDB keep the table's own columns: read get_collection for the keys. YouTube videos go in videos (the posts fields plus videoId; block latest-videos), podcast episodes in episodes (plus audioUrl, duration; block podcast-episodes), Bluesky and Mastodon posts in posts. Shopify, WooCommerce, Square, Gumroad and Lemon Squeezy fill products (the Stripe product shape plus buyUrl and storeName): the product blocks show "Buy on <store>" for those rows instead of the cart. Form posts can also go to an email list, CRM, chat or task tool (Mailchimp, HubSpot, Slack, Trello, Asana and others), set by the owner on the Apps screen; the form markup doesn't change. Tracking and live chat (TikTok, Clarity, Plausible, Crisp, Intercom…) are set_integrations, never hand-written script tags.