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:
- Product catalog — display inventory from Supabase or Airtable as cards, link each to a detail page with full specs, pricing, and stock status
- Client portal — gate an "orders" or "invoices" page behind login, filter by
{{currentUser.id}}, and give each customer a private order history with detail views - Member directory — pull team or community profiles from a database, display as a list with thumbnails, link to individual profile pages
- Course platform — show enrolled courses filtered by user, track progress per lesson, gate content by subscription tier
- Job board — list open positions from an API, link each to a detail page with full description, requirements, and an apply button
- Knowledge base — feed articles from a headless CMS, display as a searchable list, render each article on its own detail page
- Event listings — show upcoming events as cards, link to detail pages with schedule, speakers, and registration info
- Support ticket tracker — let logged-in users see their open tickets, click through to individual ticket detail with status and history
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
- Configure your provider in
settings/data.md— set theproviderfield - Add credentials via
sitemd config setup data - Define named data sources in the
sourcesarray - 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
- Uses PostgREST API directly
- Shares credentials with auth if both use Supabase
- Supports Row Level Security for user-scoped data
Firebase
- Uses Firestore REST API
- Shares credentials with auth if both use Firebase
Airtable
- Connects to any Airtable base
- Great for non-technical content management
REST API
- Generic adapter for any backend
- Configurable base URL and headers
- Auto-detects response format (array, data.data, data.items, data.results)
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
$79 — Premium over-ear headphones with active noise cancelling and 30-hour battery life
View Details →Mechanical Keyboard
$129 — Compact 75% layout with hot-swappable switches and RGB backlighting
View Details →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.
-
Why Markdown Still Wins in 2026
After decades of WYSIWYG editors and visual builders, plain text markdown remains the fastest way to create structured content.
-
Building Documentation Sites with Claude Code
A walkthrough of how we built the sitemd docs using the same tool that powers the product.
-
Shipping User Auth in a Static Site
How sitemd adds client-side authentication with five provider adapters while keeping the site fully static.
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.
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
data-param— which URL query parameter to read (e.g.,slugreads?slug=wireless-headphones)data-key— which data field to match against the param valuedata-field— label/value pairs rendered as a definition list
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...
Modal details
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:
- Auth handles login/signup and gives you
{{currentUser.id}} - Data sources filter by that user ID so each person sees only their own records
- Gated pages restrict access to logged-in users (or specific user types)
- 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
data-auth: required— hides the data block until the user is logged in, then sends the user's Bearer token with every request- Use
{{currentUser.id}}in source filters for user-scoped data:filter: user_id = {{currentUser.id}} - Use
{{currentUser.email}},{{currentUser.name}}, or any field returned by your auth provider'suserDataUrlwebhook - Combine with Supabase Row Level Security for defense-in-depth — even if the client-side filter were bypassed, the database enforces access
Query options
data-filter: field = value— filter results (supports =, !=, >, <, >=, <=)data-sort: field desc— sort resultsdata-limit: 12— limit number of resultsdata-paginate: true— show prev/next pagination
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.
Related
- Portals — complete, copy-pasteable portal builds: freelancer portals, SaaS admin panels, real estate listings, membership directories, and e-commerce accounts
- User Auth & Gating — authentication, gated pages, and user types
- Cards — static card components that dynamic cards mirror
- CLI Config — manage data provider credentials and other service config
- Tooltips & Modals — the modal system used by
data-detail: modal