Dynamic data

Your sitemd site isn't limited to static content. Connect a database or API and display live, auto-updating data as cards, lists, tables, or full detail pages — without writing any JavaScript. Combine it with user auth and gated pages to build client portals, member dashboards, order histories, and personalized experiences that would normally require a custom web app.

What you can build

Dynamic data turns a markdown site into something much closer to a full application. A few examples:

All of these are built with the same building blocks: a data source definition, a display mode, and optionally auth gating and detail pages.

Setup

  1. Configure your provider in settings/data.md — set the provider field
  2. Add credentials via sitemd config setup data
  3. Define named data sources in the sources array
  4. Use data: blocks in any page to display data
---
provider: supabase
cacheTTL: 300
sources:
  - name: products
    table: products
    select: id, name, description, photo_url, price, slug
    filter: active = true
    sort: name asc
---

Providers

Supabase

Firebase

Airtable

REST API

Display modes

Cards

data: products
data-display: cards
data-detail: modal
data-title: name
data-text: {{price}} — {{description}}
data-image: photo_url
data-link: View Details: #
data-detail-field: Name: name
data-detail-field: Price: {{price}}
data-detail-field: Description: description
data-detail-field: In Stock: stock_status

Map fields: data-title, data-text, data-image, data-link — same slots as static cards. With data-detail: modal, clicking "View Details" opens a modal instead of navigating.

Wireless Headphones

Wireless Headphones

$79 — Premium over-ear headphones with active noise cancelling and 30-hour battery life

View Details →
Mechanical Keyboard

Mechanical Keyboard

$129 — Compact 75% layout with hot-swappable switches and RGB backlighting

View Details →
USB-C Hub

USB-C Hub

$45 — 7-in-1 adapter with HDMI, USB-A, SD card reader, and 100W passthrough charging

View Details →

List

data: recent-posts
data-display: list
data-title: title
data-text: {{excerpt}}
data-image: cover_image
data-link: Read More: /blog/{{slug}}

Clean list with optional thumbnails. Use it for blog feeds, article indexes, or any collection where a linear layout fits better than a grid.

Table

data: my-orders
data-display: table
data-detail: modal
data-field: Order: order_number
data-field: Status: status
data-field: Total: {{currency}}{{amount}}
data-field: Date: created_at
data-link: View: #
data-detail-field: Order Number: order_number
data-detail-field: Status: status
data-detail-field: Total: {{currency}}{{amount}}
data-detail-field: Shipping: {{street}}, {{city}}, {{state}} {{zip}}
data-detail-field: Items: items_description

Uses data-field: Label: template for columns. With data-detail: modal, clicking "View" opens the full record in a modal.

OrderStatusTotalDate
ORD-4821Shipped$129.002026-03-15View
ORD-4807Delivered$45.002026-03-12View
ORD-4793Delivered$238.502026-03-08View
ORD-4756Delivered$79.002026-02-28View

Detail pages

Detail pages are where dynamic data becomes a real application. Instead of just listing records, each row in your data source gets its own page with a unique URL — a product page, an order receipt, a member profile, a job posting.

There are two ways to show record details: as a standalone page or in a modal.

Standalone detail pages

A standalone detail page has its own URL, making it linkable, shareable, and bookmarkable. Create a separate markdown file and use data-display: detail with a URL parameter to identify which record to show.

Product detail (pages/products/detail.md):

data: products
data-display: detail
data-param: slug
data-key: slug
data-field: Name: name
data-field: Price: {{price}}
data-field: Description: description
data-field: In Stock: stock_status
data-field: Category: category

Link to it from a card or list: data-link: View: /products/detail?slug={{slug}}

This is what makes dynamic data a full application pattern. A product catalog links each card to /products/detail?slug=wireless-headphones. A member directory links each name to /members/profile?id=42. A job board links each listing to /jobs/detail?id=senior-engineer. The list page and detail page share the same data source — the detail page just filters to one record.

Blog post detail (pages/blog/post.md):

data: recent-posts
data-display: detail
data-param: slug
data-key: slug
data-field: Title: title
data-field: Author: author
data-field: Published: created_at
data-field: Body: body
Title
Why Markdown Still Wins in 2026
Author
Sarah Chen
Published
2026-03-14
Body
After decades of WYSIWYG editors and visual builders, plain text markdown remains the fastest way to create structured content...

Open detail inline without leaving the page. Add data-detail: modal and data-detail-field: lines to any cards, list, or table block. No separate page needed:

data: products
data-display: cards
data-detail: modal
data-title: name
data-text: {{price}} — {{description}}
data-image: photo_url
data-link: View Details: #
data-detail-field: Name: name
data-detail-field: Price: {{price}}
data-detail-field: Description: description
data-detail-field: In Stock: stock_status

When data-detail: modal is set, clicking the link opens a modal overlay with the detail fields rendered inside — using the same modal system as Tooltips & Modals. The data-link label is used as the trigger text; the URL is ignored.

Works with all collection display modes (cards, list, table). Use modals for quick-peek details (a product spec summary, an order snapshot) and standalone pages when you need a permanent, shareable URL (a receipt a customer can bookmark, a profile link they can share).

Building personalized portals

Dynamic data, user auth, and gated pages are three features that work together to create something much bigger than any of them alone: personalized, logged-in experiences. This is how you turn a markdown site into a client portal, a student dashboard, or a team workspace.

The pattern is straightforward:

  1. Auth handles login/signup and gives you {{currentUser.id}}
  2. Data sources filter by that user ID so each person sees only their own records
  3. Gated pages restrict access to logged-in users (or specific user types)
  4. Detail pages let users drill into individual records

Example: customer order portal

A customer logs in and sees their order history. They click any order to see full details including shipping and line items. The entire experience is four things: a data source, two pages, and an auth setting.

Data source (settings/data.md):

sources:
  - name: my-orders
    table: orders
    select: id, order_number, status, currency, amount, created_at, street, city, state, zip, items_description
    filter: user_id = {{currentUser.id}}
    sort: created_at desc

The {{currentUser.id}} filter means each user only sees their own orders. This is resolved at runtime after the user logs in.

Orders list (gated-pages/orders.md):

data: my-orders
data-display: table
data-auth: required
data-field: Order: order_number
data-field: Status: status
data-field: Total: {{currency}}{{amount}}
data-field: Date: created_at
data-link: View: /account/orders/detail?id={{id}}
data-sort: created_at desc

Order detail (gated-pages/orders/detail.md):

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: {{currency}}{{amount}}
data-field: Shipping: {{street}}, {{city}}, {{state}} {{zip}}
data-field: Items: items_description

Because the detail page is in gated-pages/, it requires login automatically. And because the data source filters by {{currentUser.id}}, a user can't view someone else's order even if they guess the ID. Direct link for email: https://yoursite.com/account/orders/detail?id=abc123

Example: student course dashboard

A learning platform where students see their enrolled courses and track lesson progress.

Data source (settings/data.md):

sources:
  - name: my-courses
    table: enrollments
    select: id, course_name, course_image, progress, total_lessons, completed_lessons, next_lesson_slug
    filter: student_id = {{currentUser.id}}
    sort: course_name asc
  - name: course-lessons
    table: lessons
    select: id, title, duration, completed, video_url, content
    sort: position asc

My courses (gated-pages/courses.md):

data: my-courses
data-display: cards
data-auth: required
data-title: course_name
data-text: {{completed_lessons}}/{{total_lessons}} lessons complete
data-image: course_image
data-link: Continue: /account/courses/detail?id={{id}}

Course detail (gated-pages/courses/detail.md):

data: course-lessons
data-display: list
data-param: id
data-auth: required
data-title: title
data-text: {{duration}} min
data-link: Watch: /account/lessons/watch?id={{id}}

Example: team member workspace

An internal tool where team members see assigned tasks and project updates, with different views for different roles.

Data source (settings/data.md):

sources:
  - name: my-tasks
    table: tasks
    select: id, title, status, priority, due_date, project_name
    filter: assignee_id = {{currentUser.id}}
    sort: due_date asc

My tasks (gated-pages/dashboard.md):

data: my-tasks
data-display: table
data-auth: required
data-field: Task: title
data-field: Project: project_name
data-field: Priority: priority
data-field: Due: due_date
data-field: Status: status
data-link: Open: /account/tasks/detail?id={{id}}
data-filter: status != done

Gate different pages by user type to create role-specific views — managers see all team tasks, individual contributors see only their own. See User Auth & Gating for user type configuration.

Auth integration

Query options

Template syntax

Use {{fieldName}} anywhere in map values to interpolate data fields:

data-text: {{price}} — {{description}}
data-link: View: /products/{{slug}}
data-field: Address: {{street}}, {{city}}, {{state}} {{zip}}

Caching

Data is cached in the browser session (sessionStorage) with a configurable TTL (default: 5 minutes). Set cacheTTL in settings/data.md to control this. Set to 0 to disable caching.