> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omni.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Improving date flexibility with templated filters

> A guide to going beyond the out of the box controls to give users more flexibility to manipulate dates by utilizing templated filters.

export const categoryIcons = {
  'administration': 'lock',
  'api': 'terminal',
  'connections': 'database',
  'dashboards': 'table-columns',
  'embed': 'code',
  'errors': 'exclamation',
  'modeling': 'wrench',
  'patterns': 'plus',
  'schedules & alerts': 'envelope',
  'visualizations': 'chart-column',
  'workbooks': 'book'
};

export const GuideSidebar = ({category, relatedLinks, updatedDate}) => {
  const [progress, setProgress] = React.useState(0);
  React.useEffect(() => {
    const sidebar = document.querySelector('.guide-sidebar');
    if (!sidebar) return;
    let container = sidebar.parentElement;
    while (container && !container.querySelector('.guide-header')) {
      container = container.parentElement;
    }
    if (container && !container.classList.contains('guide-page-layout')) {
      container.classList.add('guide-page-layout');
    }
  }, []);
  React.useEffect(() => {
    const handleScroll = () => {
      const scrollTop = window.scrollY;
      const docHeight = document.documentElement.scrollHeight - window.innerHeight;
      const scrollPercent = docHeight > 0 ? scrollTop / docHeight * 100 : 0;
      setProgress(Math.min(100, Math.max(0, scrollPercent)));
    };
    window.addEventListener('scroll', handleScroll, {
      passive: true
    });
    handleScroll();
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);
  const icon = category ? categoryIcons[category.toLowerCase()] || 'book' : 'book';
  return <aside className="guide-sidebar">
      <div className="guide-sidebar-content">
        <a href="/guides" className="guide-sidebar-back">
          <Icon icon="arrow-left" iconType="solid" size={14} />
          <span>All guides</span>
        </a>

        <div className="guide-sidebar-section">
          <div className="guide-sidebar-label">Progress</div>
          <div className="guide-sidebar-progress">
            <div className="guide-mascot">
              <svg viewBox="0 0 450 450" width="48" height="48">
                <defs>
                  <clipPath id="progressClip">
                    <rect x="0" y={450 - progress * 4.5} width="450" height={progress * 4.5} />
                  </clipPath>
                  <linearGradient id="blobbyGradient" x1="55.9753" y1="0" x2="492.197" y2="169.724" gradientUnits="userSpaceOnUse">
                    <stop stopColor="#BCA2F3" />
                    <stop offset="0.572917" stopColor="#FF7AA2" />
                    <stop offset="1" stopColor="#F3D4A2" />
                  </linearGradient>
                </defs>

                {}
                <circle cx="223.901" cy="223.901" r="213.901" transform="matrix(-0.999988 -0.0049013 0.00491945 -0.999988 447.797 449.992)" fill="#FAFAFA" stroke="#480B38" strokeWidth="20" />

                {}
                <circle cx="223.901" cy="223.901" r="213.901" transform="matrix(-0.999988 -0.0049013 0.00491945 -0.999988 447.797 449.992)" fill="url(#blobbyGradient)" stroke="#480B38" strokeWidth="20" clipPath="url(#progressClip)" />

                {}
                <path d="M310.41 195.084C310.41 200.052 301.362 212.472 284.328 212.472C266.585 212.472 258.246 201.294 258.246 195.912" stroke="#480B38" strokeWidth="17.3883" strokeMiterlimit="1.33344" strokeLinecap="round" />
                <circle cx="21.168" cy="21.168" r="21.168" transform="matrix(-1 0 0 1 388.658 169.001)" fill="#480B38" />
                <circle cx="21.168" cy="21.168" r="21.168" transform="matrix(-1 0 0 1 223.467 169.001)" fill="#480B38" />
              </svg>
            </div>
            <span className="guide-sidebar-progress-text">{Math.round(progress)}%</span>
          </div>
        </div>

        {category && <div className="guide-sidebar-section">
            <div className="guide-sidebar-label">Category</div>
            <div className="guide-sidebar-category">
              <Icon icon={icon} iconType="solid" size={14} />
              <span>{category}</span>
            </div>
          </div>}

        {updatedDate && <div className="guide-sidebar-section">
            <div className="guide-sidebar-label">Last updated</div>
            <div className="guide-sidebar-date">{updatedDate}</div>
          </div>}

        {relatedLinks && relatedLinks.length > 0 && <div className="guide-sidebar-section">
            <div className="guide-sidebar-label">Related</div>
            <ul className="guide-sidebar-links">
              {relatedLinks.map((link, index) => <li key={index}>
                  <a href={link.href}>{link.title}</a>
                </li>)}
            </ul>
          </div>}
      </div>
    </aside>;
};

export const GuideTitle = ({title}) => {
  return <div className="guide-header">
      <h1 className="guide-title">{title}</h1>
    </div>;
};

<GuideSidebar
  categoryIcons={categoryIcons}
  category="modeling"
  updatedDate="April 2026"
  relatedLinks={[
{ title: "Templated filters", href: "/modeling/templated-filters" },
{ title: "Filter-only fields", href: "/modeling/templated-filters/parameters" },
{ title: "Dashboard controls", href: "/visualize-present/dashboards/controls" },
{ title: "Dashboard filters", href: "/visualize-present/dashboards/filters" }
]}
/>

<GuideTitle title="Improving date flexibility with templated filters" />

Omni provides powerful, built-in options for manipulating dates on a dashboard, such as [time frame switchers](/visualize-present/dashboards/controls) and [period over period analysis](/visualize-present/dashboards/controls).

However, if you want to give your users even more control, [templated filters](/modeling/templated-filters) are the answer. This guide walks through two advanced patterns for date flexibility:

1. **Granularity control with automated filtering:** A single control that changes the granularity of time series charts (e.g., month to quarter) while simultaneously filtering to the "last complete" period.
2. **The "everything" toggles:** Giving you the ability to swap the underlying date field, timeframe granularity, and date range all from a single dashboard interface using multiple filters.

Implementing these patterns allows you to create a system where you can redefine the context of your data on the fly.

## Requirements

To follow the steps in this guide, you'll need:

* **Modeler** or **Connection Admin** permissions for the model you want to work with
* A basic understanding of [templated filters](/modeling/templated-filters)

## Pattern 1: Dynamic charts with "last complete period" logic

When looking at data over time, comparing a partial current month against a full previous month can be misleading. This pattern creates a dimension that automatically nulls out the current, incomplete period based on the granularity you've selected.

<Steps titleSize="h3">
  <Step title="Create a filter-only field for granularity" noAnchor>
    In your model, define a string filter that will act as the toggle:

    ```yaml theme={null}
      date_granularity:
        type: string
        label: Date Granularity
        description: Select the timeframe for the completion filter
        suggestion_list:
          - value: Month
          - value: Quarter
          - value: Year
        filter_single_select_only: true
    ```
  </Step>

  <Step title="Build the dynamic date dimension" noAnchor>
    Next, create a custom dimension using templated filters. This dimension will evaluate the user's selection and apply the "last complete" logic.

    For example, when `Quarter` is selected, the `CASE` statement returns `NULL` for the current (incomplete) quarter and truncates to the quarter otherwise. The same pattern applies for `Year` and `Month`.

    Use the following code to add the dimension, replacing `saas__opportunities.created_date` with your own view and date field:

    ```yaml wrap theme={null}
      created_date_completed_periods:
        sql: |-
          CASE
            WHEN {{# saas__opportunities.date_granularity.filter }} 'Quarter' {{/ saas__opportunities.date_granularity.filter }}
              THEN CASE
                WHEN DATE_TRUNC('quarter', ${saas__opportunities.created_date}) >= DATE_TRUNC('quarter', CURRENT_DATE)
                  THEN NULL
                ELSE DATE_TRUNC('quarter', ${saas__opportunities.created_date})
              END
            WHEN {{# saas__opportunities.date_granularity.filter }} 'Year' {{/ saas__opportunities.date_granularity.filter }}
              THEN CASE
                WHEN DATE_TRUNC('year', ${saas__opportunities.created_date}) >= DATE_TRUNC('year', CURRENT_DATE)
                  THEN NULL
                ELSE DATE_TRUNC('year', ${saas__opportunities.created_date})
              END
            ELSE CASE
              WHEN DATE_TRUNC('month', ${saas__opportunities.created_date}) >= DATE_TRUNC('month', CURRENT_DATE)
                THEN NULL
              ELSE DATE_TRUNC('month', ${saas__opportunities.created_date})
            END
          END
        label: Created Date (Completed Periods)
    ```
  </Step>

  <Step title="Implement on a dashboard" noAnchor>
    1. In a workbook, **select** the `Created Date (Completed Periods)` dimension for your time series charts.
    2. Navigate to your dashboard and click **Add > Filter**.
    3. Configure the filter to point at your `date_granularity` field.

    Now when the filter is used, the connected tiles will automatically update to use the selected granularity:

    <Frame caption="The granularity switcher in action, automatically hiding the current incomplete period.">
      <img src="https://mintcdn.com/omni-e7402367/qVe9zcmXNMqNIVTe/guides/modeling/images/date-granularity.gif?s=8c01ab0b5537fe89aa07631bebc159bb" alt="Date Granularity" width="800" height="624" data-path="guides/modeling/images/date-granularity.gif" />
    </Frame>
  </Step>
</Steps>

## Pattern 2: The "everything" toggles

This pattern gives you three independent dashboard controls: the date field (e.g., switching from **Created Date** to **Shipped Date**), the granularity, and the date range window.

<Steps titleSize="h3">
  <Step title="Define the selection filters" noAnchor>
    In your model, create two [filter-only fields](/modeling/templated-filters/parameters): one for the field choice and one for the granularity.

    ```yaml wrap theme={null}
      as_of_date_field:
        type: string
        label: As of Date
        suggestion_list:
          - value: Created At
          - value: Shipped At
          - value: Delivered At
          - value: Returned At
        default_filter:
          is: Created At
        filter_single_select_only: true

      as_of_date_field_granularity:
        type: string
        label: Date Granularity
        suggestion_list:
          - value: Daily
          - value: Weekly
          - value: Monthly
        default_filter:
          is: Daily
        filter_single_select_only: true
    ```
  </Step>

  <Step title="Create the dynamic logic" noAnchor>
    This pattern requires two dimensions to work: one to swap the raw date field, and the second to truncate that field based on the selected granularity.

    <Tabs>
      <Tab title="Dimension 1: Swap raw date field">
        The `dynamic_date` dimension uses a `CASE` statement to swap the underlying source column (for example, `created_at` vs. `shipped_at`) based on the filter selection:

        ```yaml wrap theme={null}
          dynamic_date:
            sql: |-
              CASE
                WHEN {{# order_items.as_of_date_field.filter }} 'Created At' {{/ order_items.as_of_date_field.filter }} THEN ${order_items.created_at}
                WHEN {{# order_items.as_of_date_field.filter }} 'Shipped At' {{/ order_items.as_of_date_field.filter }} THEN ${order_items.shipped_at}
                WHEN {{# order_items.as_of_date_field.filter }} 'Delivered At' {{/ order_items.as_of_date_field.filter }} THEN ${order_items.delivered_at}
                WHEN {{# order_items.as_of_date_field.filter }} 'Returned At' {{/ order_items.as_of_date_field.filter }} THEN ${order_items.returned_at}
              ELSE ${order_items.created_at}
              END
            label: Dynamic Date
        ```
      </Tab>

      <Tab title="Dimension 2: Truncate field">
        The `dynamic_date_granularity` dimension takes the result of `dynamic_date` and applies the truncation level (daily, weekly, or monthly) that you've selected:

        ```yaml wrap theme={null}
          dynamic_date_granularity:
            sql: |-
              CASE
                WHEN {{# order_items.as_of_date_field_granularity.filter }} 'Daily' {{/ order_items.as_of_date_field_granularity.filter }} THEN ${order_items.dynamic_date[date]}
                WHEN {{# order_items.as_of_date_field_granularity.filter }} 'Weekly' {{/ order_items.as_of_date_field_granularity.filter }} THEN ${order_items.dynamic_date[week]}
                WHEN {{# order_items.as_of_date_field_granularity.filter }} 'Monthly' {{/ order_items.as_of_date_field_granularity.filter }} THEN ${order_items.dynamic_date[month]}
              ELSE ${order_items.dynamic_date[date]}
              END
            label: Dynamic Date Granularity
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Build the view" noAnchor>
    1. In a workbook, build your charts using the `Dynamic Date Granularity` dimension.
    2. When finished, complete the following on the document's dashboard:
       1. Add filters to the dashboard for both `As of Date` and `Date Granularity`.
       2. Add a standard date range filter and [map it](/visualize-present/dashboards/filters#mapping-filters-to-differing-fields-on-tiles) to the `Dynamic Date Granularity` field to control the window of time.

    <Frame caption="Swapping date fields, granularity, and date ranges all from the dashboard UI.">
      <img src="https://mintcdn.com/omni-e7402367/qVe9zcmXNMqNIVTe/guides/modeling/images/everything-toggle.gif?s=a0b707638ba1af43a20353a8345455b7" alt="Everything Toggle" width="800" height="448" data-path="guides/modeling/images/everything-toggle.gif" />
    </Frame>
  </Step>
</Steps>

## Next steps

* Learn more about [filter-only fields](/modeling/templated-filters/parameters) to create more interactive toggles.
* Learn how to [map dashboard filters to different tile fields](/visualize-present/dashboards/filters) to ensure your filters apply correctly across tiles.
