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

# Reference files in expressions

> Use file data from Assets and Runs in Rule and Calculated Channel expressions instead of hardcoding values.

export const MintTable = ({columns = [], rows = [], columnWidths = []}) => {
  const pushTextWithLineBreaks = (parts, text, keyBase) => {
    const segments = String(text).split(/\\n|\n/);
    segments.forEach((segment, idx) => {
      if (segment) {
        parts.push(<span key={`${keyBase}-text-${idx}`}>{segment}</span>);
      }
      if (idx < segments.length - 1) {
        parts.push(<br key={`${keyBase}-br-${idx}`} />);
      }
    });
  };
  const parseMarkdown = text => {
    if (text === null || text === undefined) return "";
    const str = String(text);
    const parts = [];
    let lastIndex = 0;
    const pattern = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[([^\]]+)\]\(([^)]+)\))/g;
    let match;
    while (true) {
      match = pattern.exec(str);
      if (match === null) {
        break;
      }
      if (match.index > lastIndex) {
        pushTextWithLineBreaks(parts, str.substring(lastIndex, match.index), `before-${lastIndex}`);
      }
      const fullMatch = match[0];
      if (fullMatch.startsWith("`") && fullMatch.endsWith("`")) {
        parts.push(<code key={match.index}>{fullMatch.slice(1, -1)}</code>);
      } else if (fullMatch.startsWith("**") && fullMatch.endsWith("**")) {
        parts.push(<strong key={match.index}>{fullMatch.slice(2, -2)}</strong>);
      } else if (fullMatch.startsWith("*") && fullMatch.endsWith("*")) {
        parts.push(<em key={match.index}>{fullMatch.slice(1, -1)}</em>);
      } else if (fullMatch.startsWith("[")) {
        const linkText = match[2];
        const linkUrl = match[3];
        parts.push(<a key={match.index} href={linkUrl} className="text-black-600 dark:text-black-400">
            {linkText}
          </a>);
      }
      lastIndex = pattern.lastIndex;
    }
    if (lastIndex < str.length) {
      pushTextWithLineBreaks(parts, str.substring(lastIndex), `tail-${lastIndex}`);
    }
    if (parts.length > 0) {
      return parts;
    }
    const plainParts = [];
    pushTextWithLineBreaks(plainParts, str, "plain");
    return plainParts.length ? plainParts : str;
  };
  const safeColumns = Array.isArray(columns) ? columns : [];
  const safeRows = Array.isArray(rows) ? rows : [];
  const safeColumnWidths = Array.isArray(columnWidths) ? columnWidths : [];
  const hasColumnWidths = safeColumnWidths.some(w => w !== null && w !== undefined && w !== "");
  const toCssWidth = width => typeof width === "number" ? `${width}px` : String(width);
  const getColumnStyle = idx => {
    const rawWidth = safeColumnWidths[idx];
    if (rawWidth === null || rawWidth === undefined || rawWidth === "") {
      return undefined;
    }
    const width = toCssWidth(rawWidth);
    return {
      width,
      minWidth: width
    };
  };
  const containerStyle = hasColumnWidths ? undefined : {
    overflowX: "auto"
  };
  const tableStyle = hasColumnWidths ? {
    tableLayout: "fixed",
    width: "100%"
  } : {
    width: "max-content",
    minWidth: "100%"
  };
  if (!Array.isArray(columns) || !Array.isArray(rows) || !Array.isArray(columnWidths)) {
    console.warn("MintTable received invalid props:", {
      columns,
      rows,
      columnWidths
    });
  }
  if (!safeColumns.length && !safeRows.length) {
    return null;
  }
  return <div className="mint-table-container" style={containerStyle}>
      <table style={tableStyle}>
        {hasColumnWidths && <colgroup>
            {safeColumns.map((_, idx) => {
    const style = getColumnStyle(idx);
    return <col key={idx} style={style} />;
  })}
          </colgroup>}
        <thead>
          <tr>
            {safeColumns.map((col, idx) => <th key={idx} className="text-left" style={getColumnStyle(idx)}>
                <b>{parseMarkdown(col)}</b>
              </th>)}
          </tr>
        </thead>
        <tbody>
          {safeRows.map((row, rIdx) => {
    const safeRow = Array.isArray(row) ? row : [];
    return <tr key={rIdx}>
                {safeRow.map((cell, cIdx) => <td key={cIdx} style={getColumnStyle(cIdx)}>
                    {parseMarkdown(cell)}
                  </td>)}
              </tr>;
  })}
        </tbody>
      </table>
    </div>;
};

After completing this workflow, you can reference external file data directly in Rule and Calculated Channel expressions to compare telemetry against thresholds, apply calibration values, or use configuration data that changes between Runs without hardcoding values in your expressions.

## Before you begin

* You have an Asset or a Run with at least one file attached in a [supported format](/documentation/reference/expression-syntax#supported-file-formats).
* You have an existing Rule or Calculated Channel to edit, or you are ready to create a new one.

## How file references work

Rule and Calculated Channel expressions can access data from files attached to an Asset or a Run using two functions: `assetFile()` and `runFile()`.

* `assetFile("filename.format")` reads a file attached to the Asset the Rule or Calculated Channel is configured for.
  * The file is verified when the expression is created or edited.
  * Use this for data that stays the same across Runs, such as hardware limits, calibration offsets, or hardware specifications.
* `runFile("filename.format")` reads a file attached to the Run being evaluated.
  * The file is resolved at evaluation time, when a Rule is evaluated against a Run, or when a Calculated Channel is evaluated against a Run.
  * Use this for data that changes between Runs, such as test parameters, environmental conditions, or per-session acceptance criteria.

Once a file is loaded, you access specific values using [bracket notation](/documentation/reference/expression-syntax#bracket-notation). The syntax depends on the file format. For supported formats and syntax, see [File functions](/documentation/reference/expression-syntax#file-functions).

## Reference a file in an expression

1. Attach a file to the Asset or the Run you want the expression to reference.
2. Create a new Rule or Calculated Channel, or open an existing one to edit.
3. In the **Expression** editor, use `assetFile("filename")` or `runFile("filename")` to reference the file by name.
4. Use [bracket notation](/documentation/reference/expression-syntax#bracket-notation) to index into the specific value.
5. Verify the expression:
   * **Rules**: Preview the Rule against an existing Run to confirm the expression resolves correctly.
   * **Calculated Channels**: Preview the Calculated Channel against a Run to confirm the expression resolves correctly.

## Examples

### Rules

<MintTable
  columns={['Format', 'Use case', 'Example']}
  columnWidths={['10%', '20%', '55%']}
  rows={[
[
  'JSON',
  'Compare a Channel against a threshold stored on the Asset.\n\nCompare a Channel against a per-Run threshold.',
  '`$1 > assetFile("limits.json")["max_temp"]`\n\n\n\n`$1 > runFile("thresholds.json")["max_temp"]`'
],
[
  'YAML',
  'Compare a Channel against a threshold stored on the Asset.',
  '`$1 > assetFile("limits.yaml")["max_temp"]`'
],
[
  'CSV',
  'Compare a Channel against a per-Run threshold.\n\nCompare a Channel against a per-Run integer threshold.',
  '`$1 > runFile("data.csv")["temperature"][0]`\n\n\n\n`$1 > double(runFile("data.csv")["temperature"][0])`'
],
[
  'TXT',
  'Compare a Channel against a numeric threshold stored in a text file.',
  '`$1 > double(runFile("max_temp.txt"))`'
]
]}
/>

### Calculated Channels

<MintTable
  columns={['Format', 'Use case', 'Example']}
  columnWidths={['5%', '15%', '55%']}
  rows={[
[
  'JSON',
  'Apply a calibration offset stored on the Asset.\n\nApply a per-Run correction factor.',
  '`$1 + assetFile("calibration.json")["sensors"]["offset"]`\n\n\n\n\n`$1 * runFile("params.json")["correction_factor"]`'
],
[
  'YAML',
  'Apply a calibration offset stored on the Asset.',
  '`$1 + assetFile("calibration.yaml")["sensors"]["offset"]`'
],
[
  'CSV',
  'Apply a per-Run correction factor.\n\nApply a per-Run integer correction factor.',
  '`$1 * runFile("data.csv")["correction_factor"][0]`\n\n\n\n`$1 * double(runFile("data.csv")["correction_factor"][0])`'
],
[
  'TXT',
  'Apply a per-Run offset stored in a text file.',
  '`$1 + double(runFile("offset.txt"))`'
]
]}
/>

## Reference

* [File functions](/documentation/reference/expression-syntax#file-functions)


## Related topics

- [CEL expression](/documentation/reference/expression-syntax.md)
- [Sift CLI reference](/documentation/reference/cli-reference.md)
- [remote_files](/api/reference/protocol-buffers/remote_files.md)
