Portals

Dynamic data, user auth, and gated pages combine to create full application experiences — without writing JavaScript. This page walks through complete, copy-pasteable portal examples. Each one includes the data source config, the listing page, and the detail page.

For the core syntax reference, see Dynamic Data. For auth setup, see User Auth & Gating.

How portals work

Every portal on this page is built from the same three layers:

  1. A listing page shows a collection of records — as cards, a table, or a list. This is the user's dashboard: their orders, projects, listings, whatever the portal is for.
  2. A detail view shows one record in full. It can be a standalone page with its own URL (/orders/detail?id=4821) or a modal that opens inline without navigating away.
  3. Auth gating scopes everything to the logged-in user. A {{currentUser.id}} filter on the data source ensures each person sees only their own records. Putting pages in gated-pages/ enforces login.

Detail pages vs detail modals

The detail layer is where portals become useful. Without it, you just have a list. With it, users can drill into any record and see the full picture.

Standalone detail pages get their own URL. Use them when the detail needs to be bookmarkable, shareable, or linkable from an email. An order receipt, a property listing, a member profile — anything a user might want to come back to directly.

data: my-orders
data-display: detail
data-param: id
data-key: id
data-auth: required
data-field: Order Number: order_number
data-field: Status: status
data-field: Total: ${{total}}
data-field: Shipping: {{street}}, {{city}}, {{state}} {{zip}}

The data-param reads a URL query parameter (?id=4821), data-key matches it against a field in the data source, and the page renders that single record.

Detail modals open inline on the same page. Use them for quick-peek details where navigating away would break the flow — scanning a table of support tickets, previewing a product from a grid, checking an order without leaving the dashboard.

data: support-tickets
data-display: table
data-detail: modal
data-field: Subject: subject
data-field: Status: status
data-link: View: #
data-detail-field: Subject: subject
data-detail-field: Priority: priority
data-detail-field: Assigned To: assigned_to

Add data-detail: modal and data-detail-field: lines to any cards, list, or table block. Clicking the link opens the modal with the full record — no separate page needed.

Combining both on one page

A single page can have multiple data blocks. A project detail page can show the project record as a detail view at the top and its invoices as a table with modal details below it. Every example on this page demonstrates this kind of composition.


Freelancer client portal

A freelancer shares project status, invoices, and files with each client. Every client logs in and sees only their own projects. Each project links to a detail page with deliverables, timeline, and an invoice download.

Data sources

# settings/data.md
sources:
  - name: client-projects
    table: projects
    select: id, name, status, start_date, due_date, budget, thumbnail_url, slug
    filter: client_id = {{currentUser.id}}
    sort: due_date desc

  - name: project-invoices
    table: invoices
    select: id, invoice_number, amount, status, issued_date, pdf_url, project_id
    filter: client_id = {{currentUser.id}}
    sort: issued_date desc

Projects list

The client's dashboard — shows all their active and completed projects as cards.

File: gated-pages/projects.md

---
title: My Projects
slug: /account/projects
---
data: client-projects
data-display: cards
data-auth: required
data-title: name
data-text: {{status}} · Due {{due_date}}
data-image: thumbnail_url
data-link: View Project: /account/projects/detail?slug={{slug}}
Brand Refresh

Brand Refresh 2026

In Progress · Due 2026-04-15

View Project →
Product Launch Site

Product Launch Site

Review · Due 2026-04-01

View Project →
Q1 Campaign

Q1 Campaign Assets

Delivered · Due 2026-02-28

View Project →

Project detail

Each project gets its own page with full specs, timeline, and a list of invoices.

File: gated-pages/projects/detail.md

data: client-projects
data-display: detail
data-param: slug
data-key: slug
data-auth: required
data-field: Project: name
data-field: Status: status
data-field: Started: start_date
data-field: Due: due_date
data-field: Budget: {{budget}}

Below the project detail, show invoices for that project:

data: project-invoices
data-display: table
data-auth: required
data-filter: project_id = {{param.id}}
data-field: Invoice: invoice_number
data-field: Amount: {{amount}}
data-field: Status: status
data-field: Issued: issued_date
data-link: Download PDF: {{pdf_url}}

SaaS admin panel

An admin dashboard for a SaaS product. Team members manage users, view subscription metrics, and handle support tickets. Different user types see different views.

Data sources

# settings/data.md
sources:
  - name: team-users
    table: users
    select: id, email, name, avatar_url, plan, status, created_at, last_login
    sort: created_at desc

  - name: support-tickets
    table: tickets
    select: id, subject, requester_email, status, priority, created_at, assigned_to
    sort: created_at desc

  - name: subscriptions
    table: subscriptions
    select: id, user_email, plan, amount, interval, status, next_billing, started_at
    sort: next_billing asc

User management

File: gated-pages/admin/users.md

---
title: Users
slug: /admin/users
gated: admin
---
data: team-users
data-display: table
data-auth: required
data-field: Name: name
data-field: Email: email
data-field: Plan: plan
data-field: Status: status
data-field: Joined: created_at
data-link: View: /admin/users/detail?id={{id}}
data-paginate: true
data-limit: 25
NameEmailPlanStatusJoined
Alice Parkalice@example.comProActive2025-11-03View
Ben Torresben@example.comStarterActive2025-12-18View
Carla Nguyencarla@example.comProChurned2025-09-22View
David Kimdavid@example.comEnterpriseActive2026-01-07View
Eva Johanssoneva@example.comStarterTrial2026-03-19View

User detail

File: gated-pages/admin/users/detail.md

data: team-users
data-display: detail
data-param: id
data-key: id
data-auth: required
data-field: Name: name
data-field: Email: email
data-field: Plan: plan
data-field: Status: status
data-field: Joined: created_at
data-field: Last Login: last_login

Support tickets

File: gated-pages/admin/tickets.md

data: support-tickets
data-display: table
data-auth: required
data-detail: modal
data-field: Subject: subject
data-field: From: requester_email
data-field: Priority: priority
data-field: Status: status
data-field: Created: created_at
data-link: View: #
data-detail-field: Subject: subject
data-detail-field: From: requester_email
data-detail-field: Priority: priority
data-detail-field: Status: status
data-detail-field: Assigned To: assigned_to
data-detail-field: Created: created_at
data-filter: status != closed
data-sort: priority desc
SubjectFromPriorityStatusCreated
Can't export CSV from dashboardalice@example.comHighOpen2026-03-23View
Billing page shows wrong amountben@example.comHighIn Progress2026-03-22View
Feature request: dark modedavid@example.comLowOpen2026-03-20View
SSO setup not workingeva@example.comMediumOpen2026-03-19View

Role-based access

Gate different admin sections by user type in your page frontmatter:

# Full admin access
gated: admin

# Support team only
gated: admin, support

# View-only for managers
gated: admin, manager

See User Types for how to define and assign user types.


Real estate listings portal

A property listing site where agents publish listings and buyers browse. Public listing cards, detailed property pages, and a gated agent dashboard for managing listings.

Data sources

# settings/data.md
sources:
  - name: listings
    table: properties
    select: id, address, city, state, price, beds, baths, sqft, photo_url, slug, status, listed_date
    filter: status = active
    sort: listed_date desc

  - name: my-listings
    table: properties
    select: id, address, city, price, status, views, inquiries, listed_date, slug
    filter: agent_id = {{currentUser.id}}
    sort: listed_date desc

Public property browse

This page is public — no login required. Visitors browse active listings as cards.

File: pages/properties.md

---
title: Properties for Sale
slug: /properties
---
data: listings
data-display: cards
data-title: address
data-text: {{city}}, {{state}} · {{beds}} bd / {{baths}} ba · {{sqft}} sqft
data-image: photo_url
data-link: ${{price}}: /properties/detail?slug={{slug}}
data-limit: 12
data-paginate: true
Property 1

742 Evergreen Terrace

Springfield, IL · 4 bd / 2 ba · 2,400 sqft

$425,000 →
Property 2

221B Baker Street

London, UK · 3 bd / 2 ba · 1,800 sqft

$1,250,000 →
Property 3

1640 Riverside Drive

Hill Valley, CA · 5 bd / 3 ba · 3,200 sqft

$875,000 →

Property detail

File: pages/properties/detail.md

data: listings
data-display: detail
data-param: slug
data-key: slug
data-field: Address: address
data-field: Location: {{city}}, {{state}}
data-field: Price: ${{price}}
data-field: Bedrooms: beds
data-field: Bathrooms: baths
data-field: Square Feet: sqft
data-field: Status: status
data-field: Listed: listed_date

Agent dashboard

Logged-in agents see their own listings with performance metrics.

File: gated-pages/agent/listings.md

data: my-listings
data-display: table
data-auth: required
data-field: Address: address
data-field: City: city
data-field: Price: ${{price}}
data-field: Status: status
data-field: Views: views
data-field: Inquiries: inquiries
data-link: Details: #
data-detail: modal
data-detail-field: Address: address
data-detail-field: City: city
data-detail-field: Price: ${{price}}
data-detail-field: Status: status
data-detail-field: Views: views
data-detail-field: Inquiries: inquiries
data-detail-field: Listed: listed_date
AddressCityPriceStatusViewsInquiries
742 Evergreen TerraceSpringfield$425,000Active34212Details
1640 Riverside DriveHill Valley$875,000Active1895Details
31 Spooner StreetQuahog$310,000Pending56723Details

Membership directory

A professional community with public member profiles and private member-only features. Members browse the directory, view profiles, and access gated resources.

Data sources

# settings/data.md
sources:
  - name: members
    table: members
    select: id, name, title, company, avatar_url, bio, location, website, slug
    filter: visibility = public
    sort: name asc

  - name: member-resources
    table: resources
    select: id, title, description, category, file_url, created_at
    sort: created_at desc

Member directory

Public directory showing all visible members.

File: pages/members.md

data: members
data-display: cards
data-title: name
data-text: {{title}} at {{company}}
data-image: avatar_url
data-link: View Profile: /members/profile?slug={{slug}}
data-paginate: true
data-limit: 24
Sarah Chen

Sarah Chen

Principal Engineer at Vercel

View Profile →
Marcus Rivera

Marcus Rivera

Design Lead at Figma

View Profile →
Priya Patel

Priya Patel

CTO at Stripe

View Profile →

Member profile

File: pages/members/profile.md

data: members
data-display: detail
data-param: slug
data-key: slug
data-field: Name: name
data-field: Title: title
data-field: Company: company
data-field: Location: location
data-field: Bio: bio
data-field: Website: website

Gated resources

Members-only resource library, visible after login.

File: gated-pages/resources.md

data: member-resources
data-display: list
data-auth: required
data-title: title
data-text: {{category}} · {{description}}
data-link: Download: {{file_url}}
  • 2026 Industry Salary Report

    Research · Comprehensive salary data across 12 engineering roles and 40 metro areas

  • Startup Fundraising Playbook

    Guide · Seed to Series B — term sheets, pitch decks, and investor outreach templates

  • Remote Team Toolkit

    Template · Async standup templates, decision logs, and meeting-free week schedules


E-commerce customer account

A customer account area for an online store. Order history, saved addresses, and wishlist — all filtered to the logged-in user.

Data sources

# settings/data.md
sources:
  - name: my-orders
    table: orders
    select: id, order_number, status, total, item_count, created_at, tracking_url
    filter: customer_id = {{currentUser.id}}
    sort: created_at desc

  - name: my-addresses
    table: addresses
    select: id, label, street, city, state, zip, is_default
    filter: customer_id = {{currentUser.id}}
    sort: is_default desc

  - name: my-wishlist
    table: wishlist_items
    select: id, product_name, product_image, product_price, product_slug, added_at
    filter: customer_id = {{currentUser.id}}
    sort: added_at desc

Order history

File: gated-pages/account/orders.md

data: my-orders
data-display: table
data-auth: required
data-detail: modal
data-field: Order: order_number
data-field: Items: item_count
data-field: Total: ${{total}}
data-field: Status: status
data-field: Date: created_at
data-link: Details: #
data-detail-field: Order Number: order_number
data-detail-field: Status: status
data-detail-field: Total: ${{total}}
data-detail-field: Items: item_count
data-detail-field: Tracking: tracking_url
data-paginate: true
OrderItemsTotalStatusDate
ORD-91823$247.00Delivered2026-03-18Details
ORD-91451$89.00Shipped2026-03-12Details
ORD-90235$412.50Delivered2026-02-28Details
ORD-88912$156.00Delivered2026-02-14Details

Wishlist

File: gated-pages/account/wishlist.md

data: my-wishlist
data-display: cards
data-auth: required
data-title: product_name
data-text: ${{product_price}}
data-image: product_image
data-link: View Product: /products/detail?slug={{product_slug}}
Watch

Minimalist Watch

$195.00

View Product →
Sneakers

Running Sneakers

$129.00

View Product →
Backpack

Canvas Backpack

$78.00

View Product →

Saved addresses

File: gated-pages/account/addresses.md

data: my-addresses
data-display: list
data-auth: required
data-title: label
data-text: {{street}}, {{city}}, {{state}} {{zip}}
  • Home (Default)

    742 Evergreen Terrace, Springfield, IL 62704

  • Office

    100 Industrial Way, Suite 400, Springfield, IL 62711


Building your own portal

Every example on this page follows the same pattern:

  1. Define data sources in settings/data.md — one per collection, with {{currentUser.id}} filters for user-scoped data
  2. Create listing pages with data-display: cards, list, or table — put them in gated-pages/ if they require login
  3. Create detail pages with data-display: detail and data-param/data-key for URL-based record lookup
  4. Gate by user type with gated: admin, member in frontmatter for role-based access

Mix and match display modes on the same page. A project detail page can show the project record as a detail view and its invoices as a table below it. A course page can show the course info as detail fields and its lessons as a list.

The data sources, display modes, and auth gating are all documented in detail: