# How to Use Jinja Templates in Semantic Models for Row-Level Security in Dat

> Learn to implement row-level security in Dat semantic models using Jinja 2 expressions. Inject static SQL filters with pre-build rendering and security context variables.

- Repository: [Junjie.M/dat](https://github.com/junjiem/dat)
- Tags: how-to-guide
- Published: 2026-03-05

---

**You implement row-level security in Dat by embedding Jinja 2 expressions inside the `model` field of your semantic model YAML, which gets rendered during the pre-build phase with security context variables to inject static SQL filters.**

Dat is an open-source data agent framework that uses semantic models to define virtual tables for natural language-to-SQL translation. By using **Jinja templates in semantic models for row-level security**, you can dynamically filter data at the model level without writing custom application logic, ensuring that every query respects tenant or user boundaries.

## Understanding Semantic Models and the `model` Field

In Dat, a semantic model is declared in a project-level YAML file. The critical field is **`model`**, which holds the SQL that defines the virtual table for the model.

The `model` string can contain **Jinja 2** expressions using the `{{ … }}` and `{% … %}` syntax. During the **pre-build phase**, Dat parses the project, loads the YAML, and renders the template with a map of variables that includes any runtime security context (e.g., the current user, tenant, or request-scoped parameters).

```yaml
semantic_models:
  - name: sales
    description: Sales facts
    model: |
      SELECT *
      FROM raw_sales
      {% if row_level_security %}
      WHERE region_id = {{ user.region_id }}
      {% endif %}

```

## The Jinja Rendering Pipeline

The rendering process occurs in distinct steps to ensure security filters are baked into the model definition before any queries execute.

### Step 1: Project-Level Variable Substitution

First, `DatProjectUtil.validate()` calls `JinjaTemplateUtil.render()` to process the raw project YAML. This step substitutes project-level variables such as `${project_name}`.

```java
// dat-sdk/src/main/java/ai/dat/core/utils/DatProjectUtil.java
DatProjectUtil.validate() → JinjaTemplateUtil.render()

```

### Step 2: Semantic Model Rendering

Next, `PreBuildValidator.validate()` iterates through each `SemanticModel` and renders the `model` field again, this time with the **security context variables** map.

```java
// dat-sdk/src/main/java/ai/dat/boot/PreBuildValidator.java
for (SemanticModel semanticModel : semanticModels) {
    // `variables` contains the security context (e.g., tenant_id)
    semanticModel.setModel(
        JinjaTemplateUtil.render(semanticModel.getModel(), variables));
}

```

### Step 3: SQL Generation and Validation

Finally, `SemanticModelUtil.semanticModelSql()` builds the final SQL that will be sent to the database. The built SQL is validated using `SELECT 1 FROM (<rendered_sql>)` to ensure the rendered security filter does not break the query syntax.

```java
// Validation step
SELECT 1 FROM (<rendered_sql>)

```

If validation fails, a `ValidationException` is thrown, preventing a broken security rule from being deployed.

## Implementing Row-Level Security with Jinja Variables

Because rendering happens **once per build**, the security filter is baked into the model definition and thereafter enforced by the database engine for all downstream queries (e.g., `ASKDATA` agents, OpenAPI calls).

### 1. Pass Security Variables

When you start a project (`dat run …`), supply a JSON file or command-line overrides that define a `variables` map.

```bash
dat run --variables '{"user": {"id": 42, "region_id": 7}}'

```

### 2. Jinja Resolves Placeholders

In the example above, `{{ user.region_id }}` becomes `7` during the pre-build phase.

### 3. SQL Contains a Static Filter

The built model SQL is essentially:

```sql
SELECT * FROM raw_sales WHERE region_id = 7

```

This filter is automatically applied whenever the model is queried, providing **row-level security** without additional application logic.

## Key Source Files and Implementation Details

The Jinja rendering and security implementation relies on the following core files in the `junjiem/dat` repository:

- **[`dat-core/src/main/java/ai/dat/core/semantic/data/SemanticModel.java`](https://github.com/junjiem/dat/blob/main/dat-core/src/main/java/ai/dat/core/semantic/data/SemanticModel.java)** – Defines the `SemanticModel` POJO, including the `model` property that holds the SQL template.

- **[`dat-core/src/main/java/ai/dat/core/utils/JinjaTemplateUtil.java`](https://github.com/junjiem/dat/blob/main/dat-core/src/main/java/ai/dat/core/utils/JinjaTemplateUtil.java)** – Wrapper around HubSpot’s **Jinjava** engine that performs the actual template rendering via `render(String template, Map<String, Object> variables)`.

- **[`dat-sdk/src/main/java/ai/dat/boot/PreBuildValidator.java`](https://github.com/junjiem/dat/blob/main/dat-sdk/src/main/java/ai/dat/boot/PreBuildValidator.java)** – Validates and renders semantic models before the build, calling `JinjaTemplateUtil.render()` for each model’s SQL template.

- **[`dat-sdk/src/main/java/ai/dat/core/utils/DatProjectUtil.java`](https://github.com/junjiem/dat/blob/main/dat-sdk/src/main/java/ai/dat/core/utils/DatProjectUtil.java)** – Loads the project YAML and applies global Jinja rendering for project-level variables.

- **[`dat-agents/dat-agent-agentic/src/main/java/ai/dat/agent/agentic/Text2SqlContentInjector.java`](https://github.com/junjiem/dat/blob/main/dat-agents/dat-agent-agentic/src/main/java/ai/dat/agent/agentic/Text2SqlContentInjector.java)** – Demonstrates runtime injection of Jinja-rendered models into natural language-to-SQL pipelines.

## Summary

- **Jinja templates** in the `model` field of Dat semantic models enable dynamic SQL generation based on runtime security context.
- The **`JinjaTemplateUtil.render()`** method processes templates during the pre-build phase, substituting variables like `{{ user.region_id }}` with actual values.
- **Row-level security** is enforced by baking static filters into the model SQL at build time, ensuring all downstream queries (including `ASKDATA` agents) respect tenant or user boundaries.
- The rendering pipeline validates SQL syntax before deployment, preventing broken security rules from reaching production.

## Frequently Asked Questions

### What is the syntax for Jinja templates in Dat semantic models?

Dat uses standard **Jinja 2** syntax. Use `{{ variable_name }}` for variable interpolation and `{% if condition %}` for control flow. For example: `WHERE tenant_id = {{ tenant_id }}` or `{% if row_level_security %}WHERE user_id = {{ user.id }}{% endif %}`.

### How do I pass security variables when running a Dat project?

Pass a JSON map using the `--variables` flag when executing `dat run`. For example: `dat run --variables '{"user": {"id": 42, "region_id": 7}, "tenant_id": 12}'`. These variables become available in the Jinja context during the pre-build rendering phase.

### When does the Jinja template rendering occur in the Dat build process?

Rendering occurs during the **pre-build phase**, specifically in `PreBuildValidator.validate()`. This happens after project loading but before SQL compilation, ensuring that security filters are resolved and validated (via `SELECT 1` checks) before the model is stored in the build state.

### Can I use complex logic like loops in Jinja templates for row-level security?

Yes, the **Jinjava** engine (wrapped by `JinjaTemplateUtil`) supports full Jinja 2 syntax including `for` loops, `if` statements, and macros. However, for row-level security, simple variable interpolation and conditional blocks are most common, as they generate static SQL filters that databases can optimize effectively.