# How to Use Date Pickers in Retool

- Tool: Retool
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Retool Cloud and Self-hosted
- Last updated: March 2026

## TL;DR

Retool's Date Picker component stores dates as ISO 8601 strings by default. Configure the display format using LDML format codes (MM/dd/yyyy, yyyy-MM-dd, etc.) in the Inspector's Format field. Access the value with {{ datePicker1.value }}. For date ranges, use the Date Range Picker and read {{ dateRangePicker1.start }} and {{ dateRangePicker1.end }}.

## Configuring Date Pickers and Date Ranges in Retool

Retool provides two date selection components: the Date Picker (single date) and the Date Range Picker (start and end date pair). Both render a calendar popover when clicked and store selected dates as ISO 8601 strings internally, regardless of the display format shown to the user.

The most common configuration decisions are: what display format to show (US MM/dd/yyyy vs ISO yyyy-MM-dd), whether to manage time zones (convert between UTC and local time), and what range of dates to allow (min/max date constraints). The display format uses LDML (Locale Data Markup Language) codes, not the moment.js format codes many developers expect.

This tutorial covers both Date Picker components, LDML format codes, timezone handling, reading values in SQL queries and transformers, and setting date constraints. The timezone storage and conversion strategy is covered in more depth in the time-zones tutorial.

## Before you start

- A Retool app with a Form or canvas to place date components
- Basic familiarity with the Inspector panel and {{ }} expressions

## Step-by-step guide

### 1. Add a Date Picker and configure its display format

Drag a Date Picker component onto the canvas. In the Inspector's General section, find the Format field. This controls how the date is displayed to the user — it does not affect the underlying stored value. Use LDML format codes, not moment.js codes. Common formats: MM/dd/yyyy (US), dd/MM/yyyy (European), yyyy-MM-dd (ISO, database-friendly). The stored {{ datePicker1.value }} is always an ISO 8601 string regardless of display format.

```
// LDML format codes for the Format field:
// MM/dd/yyyy  →  03/25/2026 (US format)
// dd/MM/yyyy  →  25/03/2026 (European format)
// yyyy-MM-dd  →  2026-03-25 (ISO format)
// MMMM d, yyyy  →  March 25, 2026 (Long format)
// MMM d, yyyy  →  Mar 25, 2026 (Short month)
// EEE, MMM d  →  Wed, Mar 25 (Day name)

// Note: Use 'MM' for 2-digit month (03), 'M' for no-zero month (3)
// Use 'dd' for 2-digit day (05), 'd' for no-zero day (5)
```

**Expected result:** The Date Picker displays dates in the configured format. {{ datePicker1.value }} always returns an ISO 8601 string.

### 2. Read the date value and use it in a SQL query

The Date Picker's value is accessible as {{ datePicker1.value }}, which returns an ISO 8601 string like '2026-03-25T00:00:00.000Z'. Use this directly in SQL queries as a date parameter. Most databases accept ISO 8601 strings in parameterized queries. For databases that need specific formats, use a SQL date function to cast the value.

```
-- Using datePicker1.value in SQL queries:

-- PostgreSQL — works directly:
SELECT * FROM orders
WHERE created_at >= {{ datePicker1.value }}::date
ORDER BY created_at DESC;

-- MySQL:
SELECT * FROM orders
WHERE DATE(created_at) >= DATE({{ datePicker1.value }})
ORDER BY created_at DESC;

-- SQLite:
SELECT * FROM orders
WHERE date(created_at) >= date({{ datePicker1.value }})
ORDER BY created_at DESC;

// In a JS Query — format for display:
const dateStr = datePicker1.value;
const formatted = new Date(dateStr).toLocaleDateString('en-US', {
  year: 'numeric', month: 'long', day: 'numeric'
});
// Returns: 'March 25, 2026'
```

**Expected result:** SQL query filters results by the selected date. Changing the date in the picker re-runs the query if triggered by a Change event handler.

### 3. Set a Default Value for the Date Picker

Use the Default Value field in the Inspector to pre-fill the date. For today's date, use JavaScript's Date object in expression mode. For a fixed date, enter an ISO string. For a date relative to today (e.g., 30 days from now), compute it in the Default Value expression. Remember Default Value is evaluated at component mount time.

```
// Default Value expressions:

// Today's date:
{{ new Date().toISOString() }}

// Yesterday:
{{ new Date(Date.now() - 86400000).toISOString() }}

// 30 days from today:
{{ new Date(Date.now() + 30 * 86400000).toISOString() }}

// From a table row selection:
{{ table1.selectedRow.data.due_date }}

// From a query result:
{{ getConfig.data[0]?.default_date || new Date().toISOString() }}
```

**Expected result:** The Date Picker renders pre-filled with the computed date when the form loads.

### 4. Configure the Date Range Picker for start and end dates

Drag a Date Range Picker component for cases where users need to select a time window (report date ranges, booking periods, filter windows). Configure its format the same way as the regular Date Picker using LDML codes. The Date Range Picker exposes three values: {{ dateRangePicker1.value }} (array of [start, end]), {{ dateRangePicker1.start }} (start date ISO string), and {{ dateRangePicker1.end }} (end date ISO string). Use .start and .end in SQL queries for clarity.

```
-- SQL query using date range:
SELECT
  DATE(created_at) as date,
  COUNT(*) as order_count,
  SUM(total_amount) as revenue
FROM orders
WHERE created_at >= {{ dateRangePicker1.start }}::timestamptz
  AND created_at < {{ dateRangePicker1.end }}::timestamptz
GROUP BY DATE(created_at)
ORDER BY date ASC;

// Display in a Text component:
{{ `${new Date(dateRangePicker1.start).toLocaleDateString()} to ${new Date(dateRangePicker1.end).toLocaleDateString()}` }}
```

**Expected result:** Users select a start and end date in one calendar interaction. SQL queries filter to the selected range.

### 5. Set Min Date and Max Date to constrain selectable dates

Prevent users from selecting dates outside a valid range using the Min Date and Max Date properties in the Inspector's Validation section. Both accept ISO strings or {{ }} expressions. To prevent future dates (e.g., for a birthdate field), set Max Date to {{ new Date().toISOString() }}. To prevent past dates (e.g., for scheduling), set Min Date to {{ new Date().toISOString() }}. Dates outside the range appear greyed out in the calendar popover.

```
// Inspector → Validation → Min Date and Max Date:

// Prevent future dates (birthdates):
// Max Date: {{ new Date().toISOString() }}

// Prevent past dates (future bookings only):
// Min Date: {{ new Date().toISOString() }}

// Constrain to a specific quarter:
// Min Date: '2026-01-01T00:00:00.000Z'
// Max Date: '2026-03-31T23:59:59.999Z'

// Constrain end date to be after start date:
// On endDatePicker, Min Date: {{ startDatePicker.value }}
```

**Expected result:** Dates outside the configured min/max range are greyed out and unclickable in the calendar.

### 6. Use the 'Manage time zone' toggle for UTC storage

In the Inspector's Advanced section, find 'Manage time zone'. When enabled, Retool converts the user's local time selection to UTC before storing it in {{ datePicker1.value }}. When disabled (default), the value is treated as local time without timezone conversion. For apps used across multiple time zones, enable Manage time zone and store all dates in UTC. Pair this with the time-zones tutorial for a complete strategy on displaying dates in users' local zones.

```
// With 'Manage time zone' enabled:
// User selects '2026-03-25 09:00 AM (EST UTC-5)'
// datePicker1.value = '2026-03-25T14:00:00.000Z' (UTC)

// With 'Manage time zone' disabled:
// User selects '2026-03-25 09:00 AM'
// datePicker1.value = '2026-03-25T09:00:00.000Z' (treated as local, no conversion)

// In a transformer — convert UTC to user's local time for display:
{{ new Date(datePicker1.value).toLocaleString('en-US', { timeZone: 'America/New_York' }) }}
```

**Expected result:** With Manage time zone on, times stored in the database are always in UTC. Display conversion happens at render time.

## Complete code example

File: `SQL Query: getOrdersByDateRange`

```sql
-- getOrdersByDateRange
-- Uses Date Range Picker values to filter and aggregate orders
-- Run on page load: ON
-- Trigger: dateRangePicker1 onChange → trigger this query

SELECT
  o.id,
  o.order_number,
  c.full_name AS customer_name,
  o.total_amount,
  o.status,
  o.created_at
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE
  o.created_at >= {{ dateRangePicker1.start || '2026-01-01T00:00:00.000Z' }}::timestamptz
  AND o.created_at < {{ dateRangePicker1.end || new Date().toISOString() }}::timestamptz
  AND o.status != 'cancelled'
ORDER BY o.created_at DESC
LIMIT 500;
```

## Common mistakes

- **Using moment.js format codes (YYYY-MM-DD) in the LDML Format field — 'Y' is the week-of-year in LDML, not the 4-digit year, causing dates to display incorrectly** — undefined Fix: Use LDML codes: 'yyyy' for 4-digit year, 'MM' for 2-digit month, 'dd' for 2-digit day. The correct LDML equivalent of moment's 'YYYY-MM-DD' is 'yyyy-MM-dd'.
- **Trying to compare datePicker1.value as a string with JavaScript < or > operators — works by accident for ISO format but fails for other formats** — undefined Fix: Wrap in new Date() before comparing: new Date(datePicker1.value) > new Date(anotherDate). Pass the raw ISO string directly to SQL — most databases handle ISO 8601 comparison correctly.
- **Not adding a Change event handler — the query does not refresh when the user picks a new date, so results stay stale** — undefined Fix: Add a Change event handler on the Date Picker (Inspector → Interaction) that triggers the filtering query. For Date Range Picker, add the handler on both the start and end selection events, or use the single Change event.
- **Setting Default Value to a date string in a non-ISO format like '03/25/2026' — the Date Picker fails to parse it and renders with no selection** — undefined Fix: Always use ISO 8601 format for Default Value: '2026-03-25T00:00:00.000Z' or use new Date().toISOString() for dynamic defaults.

## Best practices

- Use LDML format codes, not moment.js codes — 'M' is month (not 'm'), 'd' is day (not 'D'), 'y' is year (not 'Y')
- Always read {{ datePicker1.value }} for database queries — it returns a reliable ISO 8601 string regardless of the display format
- Enable Manage time zone for multi-timezone apps and store dates in UTC — consistent UTC storage prevents timezone-related display bugs
- Set endDatePicker Min Date to {{ startDatePicker.value }} to prevent end-before-start selections without writing a custom validator
- Add a Change event handler to Date Pickers that trigger query refreshes so results update immediately when the user selects a new date
- Use Date Range Picker instead of two separate Date Pickers when selecting a date window — it provides a better UX and enforces start-before-end automatically
- For birthdate fields, set Max Date to today and provide a reasonable Min Date (e.g., 150 years ago) to constrain the calendar to sensible values

## Frequently asked questions

### Can I show a time selector alongside the date in the Date Picker?

Yes. In the Inspector's General section, enable the 'Show time' toggle on the Date Picker. This adds a time input below the calendar. The time selection is included in {{ datePicker1.value }} as part of the ISO 8601 string. Use LDML time format codes in the Format field to display the time: 'MM/dd/yyyy hh:mm a' for 12-hour or 'MM/dd/yyyy HH:mm' for 24-hour.

### How do I display the selected date in a different format than the Date Picker's Format setting?

Read {{ datePicker1.value }} and format it with JavaScript in a Text component or transformer: {{ new Date(datePicker1.value).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }) }}. The Format field controls what appears inside the Date Picker input box; you can format the same value differently anywhere else in the app.

### Why does my Date Picker show the date one day behind the selected value?

This is a timezone offset issue. The ISO string '2026-03-25T00:00:00.000Z' is midnight UTC, which is the evening of March 24 in UTC-8 timezones. Enable 'Manage time zone' in the Inspector's Advanced section, or use a date-only format by truncating the ISO string: {{ datePicker1.value?.split('T')[0] }} to work with the date portion only.

---

Source: https://www.rapidevelopers.com/retool-tutorial/how-to-use-date-pickers-in-retool
© RapidDev — https://www.rapidevelopers.com/retool-tutorial/how-to-use-date-pickers-in-retool
