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

# Working with project filters

> Learn how to implement and use project filters to search, prioritize, and manage projects across your organization.

export const ProjectFilterBuilder = () => {
  const COPY_FEEDBACK_MS = 2000;
  const DELETE_ICON_PX = 16;
  const PLACEHOLDER_PREVIEW = 'Enter a condition to generate a filter...';
  const nextIdRef = useRef(1);
  const makeRowId = () => {
    nextIdRef.current += 1;
    return 'pfb-row-' + String(nextIdRef.current);
  };
  const [rows, setRows] = useState([{
    id: 'pfb-row-1',
    field: '',
    operator: '',
    value: ''
  }]);
  const [copied, setCopied] = useState(false);
  const [isDark, setIsDark] = useState(false);
  useEffect(() => {
    const check = () => {
      const root = document.documentElement;
      setIsDark(root.dataset.theme === 'dark' || root.classList.contains('dark'));
    };
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['data-theme', 'class']
    });
    return () => observer.disconnect();
  }, []);
  const ENUMS = {
    platform: [{
      value: 'PLATFORM_SOURCE_GITHUB',
      label: 'GitHub'
    }, {
      value: 'PLATFORM_SOURCE_GITLAB',
      label: 'GitLab'
    }, {
      value: 'PLATFORM_SOURCE_GITSERVER',
      label: 'Git Server'
    }, {
      value: 'PLATFORM_SOURCE_BITBUCKET',
      label: 'Bitbucket'
    }, {
      value: 'PLATFORM_SOURCE_BINARY',
      label: 'Binary'
    }, {
      value: 'PLATFORM_SOURCE_HUGGING_FACE',
      label: 'Hugging Face'
    }, {
      value: 'PLATFORM_SOURCE_AZURE',
      label: 'Azure'
    }, {
      value: 'PLATFORM_SOURCE_ARCHIVE',
      label: 'Archive'
    }, {
      value: 'PLATFORM_SOURCE_EXTERNAL_AI_SERVICE',
      label: 'External AI Service'
    }, {
      value: 'PLATFORM_SOURCE_GITHUB_ENTERPRISE',
      label: 'GitHub Enterprise'
    }],
    ecosystem: [{
      value: 'ECOSYSTEM_C',
      label: 'C/C++'
    }, {
      value: 'ECOSYSTEM_CARGO',
      label: 'Cargo'
    }, {
      value: 'ECOSYSTEM_COCOAPOD',
      label: 'CocoaPods'
    }, {
      value: 'ECOSYSTEM_CONAN',
      label: 'Conan'
    }, {
      value: 'ECOSYSTEM_CONTAINER',
      label: 'Container'
    }, {
      value: 'ECOSYSTEM_GEM',
      label: 'RubyGems'
    }, {
      value: 'ECOSYSTEM_GITHUB_ACTION',
      label: 'GitHub Action'
    }, {
      value: 'ECOSYSTEM_GO',
      label: 'Go'
    }, {
      value: 'ECOSYSTEM_HUGGING_FACE',
      label: 'Hugging Face'
    }, {
      value: 'ECOSYSTEM_MAVEN',
      label: 'Maven'
    }, {
      value: 'ECOSYSTEM_NPM',
      label: 'npm'
    }, {
      value: 'ECOSYSTEM_NUGET',
      label: 'NuGet'
    }, {
      value: 'ECOSYSTEM_PACKAGIST',
      label: 'Packagist'
    }, {
      value: 'ECOSYSTEM_PYPI',
      label: 'PyPI'
    }, {
      value: 'ECOSYSTEM_SWIFT',
      label: 'Swift'
    }]
  };
  const OPERATOR_SETS = {
    string: [{
      value: '==',
      label: 'equals'
    }, {
      value: '!=',
      label: 'not equals'
    }, {
      value: 'in',
      label: 'in'
    }, {
      value: 'matches',
      label: 'matches'
    }],
    numeric: [{
      value: '==',
      label: 'equals'
    }, {
      value: '!=',
      label: 'not equals'
    }, {
      value: '>',
      label: 'greater than'
    }, {
      value: '>=',
      label: 'greater or equal'
    }, {
      value: '<',
      label: 'less than'
    }, {
      value: '<=',
      label: 'less or equal'
    }],
    boolean: [{
      value: '==',
      label: 'equals'
    }, {
      value: '!=',
      label: 'not equals'
    }],
    enum: [{
      value: '==',
      label: 'equals'
    }, {
      value: '!=',
      label: 'not equals'
    }, {
      value: 'in',
      label: 'in'
    }, {
      value: 'not in',
      label: 'not in'
    }],
    list: [{
      value: 'contains',
      label: 'contains'
    }, {
      value: 'not contains',
      label: 'not contains'
    }]
  };
  const FIELDS = [{
    value: 'uuid',
    label: 'Project UUID',
    type: 'string',
    description: 'Filters projects by their unique identifier.'
  }, {
    value: 'meta.name',
    label: 'Project Name',
    type: 'string',
    description: 'Filters projects by the display name of the project.'
  }, {
    value: 'meta.tags',
    label: 'Custom Tags',
    type: 'list',
    enumKey: null,
    description: 'Filters projects by custom tags set during project setup or scan configuration.'
  }, {
    value: 'spec.platform_source',
    label: 'Platform Source',
    type: 'enum',
    enumKey: 'platform',
    description: 'Filters projects by source platform such as GitHub, GitLab, or Bitbucket.'
  }, {
    value: 'spec.automated_scan_enabled',
    label: 'Automated Scan Enabled',
    type: 'boolean',
    description: 'Filters projects by whether automated scanning is enabled.'
  }, {
    value: 'spec.package_coverage.ecosystems',
    label: 'Package Ecosystems',
    type: 'list',
    enumKey: 'ecosystem',
    description: 'Filters projects by the language-specific package ecosystems they use.'
  }, {
    value: 'spec.package_coverage.total',
    label: 'Total Packages',
    type: 'numeric',
    description: 'Filters projects by the total number of packages resolved in the project.'
  }, {
    value: 'spec.finding_counts.total',
    label: 'Total Findings',
    type: 'numeric',
    description: 'Filters projects by the total number of findings detected.'
  }, {
    value: 'spec.finding_counts.critical',
    label: 'Critical Findings',
    type: 'numeric',
    description: 'Filters projects by the number of critical-severity findings.'
  }, {
    value: 'spec.finding_counts.high',
    label: 'High Findings',
    type: 'numeric',
    description: 'Filters projects by the number of high-severity findings.'
  }, {
    value: 'spec.finding_counts.medium',
    label: 'Medium Findings',
    type: 'numeric',
    description: 'Filters projects by the number of medium-severity findings.'
  }, {
    value: 'spec.finding_counts.low',
    label: 'Low Findings',
    type: 'numeric',
    description: 'Filters projects by the number of low-severity findings.'
  }, {
    value: 'spec.finding_counts.dismissed',
    label: 'Dismissed Findings',
    type: 'numeric',
    description: 'Filters projects by the number of findings dismissed by users. Useful for reviewing triage quality.'
  }, {
    value: 'spec.vulnerability_counts.total.total',
    label: 'Total Vulnerabilities',
    type: 'numeric',
    description: 'Filters projects by the total number of vulnerabilities across all dependencies.'
  }, {
    value: 'spec.vulnerability_counts.total.critical',
    label: 'Critical Vulnerabilities',
    type: 'numeric',
    description: 'Filters projects by the number of critical-severity vulnerabilities.'
  }, {
    value: 'spec.vulnerability_counts.total.high',
    label: 'High Vulnerabilities',
    type: 'numeric',
    description: 'Filters projects by the number of high-severity vulnerabilities.'
  }, {
    value: 'spec.vulnerability_counts.total.medium',
    label: 'Medium Vulnerabilities',
    type: 'numeric',
    description: 'Filters projects by the number of medium-severity vulnerabilities.'
  }, {
    value: 'spec.vulnerability_counts.total.low',
    label: 'Low Vulnerabilities',
    type: 'numeric',
    description: 'Filters projects by the number of low-severity vulnerabilities.'
  }, {
    value: 'spec.package_coverage.call_graph_success_rate',
    label: 'Reachability Analysis Status',
    type: 'numeric',
    description: 'Filters projects by the success rate of call graph generation and reachability analysis (0 to 1).'
  }, {
    value: 'spec.package_coverage.success_rate',
    label: 'Dependency Resolution Status',
    type: 'numeric',
    description: 'Filters projects by the percentage of resolved dependencies (0 to 1).'
  }, {
    value: 'spec.dependency_counts.direct',
    label: 'Direct Dependencies',
    type: 'numeric',
    description: 'Filters projects by the number of direct dependencies declared in manifests.'
  }, {
    value: 'spec.dependency_counts.transitive',
    label: 'Transitive Dependencies',
    type: 'numeric',
    description: 'Filters projects by the number of transitive dependencies pulled in by direct dependencies.'
  }, {
    value: 'spec.dependency_counts.outdated',
    label: 'Outdated Dependencies',
    type: 'numeric',
    description: 'Filters projects by the number of outdated dependencies.'
  }, {
    value: 'spec.dependency_counts.unmaintained',
    label: 'Unmaintained Dependencies',
    type: 'numeric',
    description: 'Filters projects by the number of unmaintained dependencies.'
  }, {
    value: 'spec.last_scanned',
    label: 'Last Scanned',
    type: 'numeric',
    description: 'Filters projects by the timestamp of the last successful scan.'
  }];
  const ENUM_FIELDS = new Set(FIELDS.filter(f => f.type === 'enum').map(f => f.value));
  const TIMESTAMP_FIELDS = new Set(['spec.last_scanned']);
  const STRING_QUOTE_FIELDS = new Set([...FIELDS.filter(f => f.type === 'string').map(f => f.value), ...FIELDS.filter(f => f.type === 'list' && !f.enumKey).map(f => f.value)]);
  const resolveFieldDef = fieldValue => FIELDS.find(f => f.value === fieldValue);
  const operatorsForFieldDef = fieldDef => {
    if (!fieldDef) {
      return OPERATOR_SETS.string;
    }
    if (fieldDef.type === 'list') {
      return OPERATOR_SETS.list;
    }
    const key = (fieldDef.type in OPERATOR_SETS) ? fieldDef.type : 'string';
    return OPERATOR_SETS[key];
  };
  const valueKindForRow = fieldDef => {
    if (!fieldDef) {
      return 'text';
    }
    if (fieldDef.type === 'boolean') {
      return 'boolean';
    }
    if (fieldDef.type === 'enum') {
      return 'enum';
    }
    if (fieldDef.type === 'list' && fieldDef.enumKey) {
      return 'enum';
    }
    return 'text';
  };
  const formatPreviewValue = (field, operator, raw) => {
    const out = raw.trim();
    if (!out) {
      return '';
    }
    if (TIMESTAMP_FIELDS.has(field) && (out.startsWith('-') || out.startsWith('+'))) {
      return 'now(' + out + ')';
    }
    if (ENUM_FIELDS.has(field)) {
      if ((operator === 'in' || operator === 'not in') && !out.startsWith('[')) {
        return '[' + out + ']';
      }
      return out;
    }
    if (STRING_QUOTE_FIELDS.has(field) && !out.startsWith('"')) {
      return '"' + out + '"';
    }
    return out;
  };
  const computePreview = () => {
    const fieldGroups = {};
    for (const row of rows) {
      const {field, operator, value} = row;
      const trimmed = typeof value === 'string' ? value.trim() : '';
      if (!field || !operator || !trimmed) {
        continue;
      }
      const formatted = formatPreviewValue(field, operator, trimmed);
      if (!formatted) {
        continue;
      }
      const clause = field + ' ' + operator + ' ' + formatted;
      if (!fieldGroups[field]) {
        fieldGroups[field] = [];
      }
      fieldGroups[field].push(clause);
    }
    const keys = Object.keys(fieldGroups);
    if (keys.length === 0) {
      return PLACEHOLDER_PREVIEW;
    }
    const parts = keys.map(field => {
      const groupConditions = fieldGroups[field];
      if (groupConditions.length > 1) {
        return '(' + groupConditions.join(' or ') + ')';
      }
      return groupConditions[0];
    });
    return parts.join(' and ');
  };
  const previewText = computePreview();
  const updateRow = (id, patch) => {
    setRows(prev => prev.map(r => r.id === id ? {
      ...r,
      ...patch
    } : r));
  };
  const handleFieldChange = (id, fieldValue) => {
    updateRow(id, {
      field: fieldValue,
      operator: '',
      value: ''
    });
  };
  const handleAddRow = () => {
    setRows(prev => [...prev, {
      id: makeRowId(),
      field: '',
      operator: '',
      value: ''
    }]);
  };
  const handleRemoveRow = id => {
    setRows(prev => {
      if (prev.length <= 1) {
        return prev;
      }
      return prev.filter(r => r.id !== id);
    });
  };
  const handleCopy = () => {
    if (!previewText || previewText === PLACEHOLDER_PREVIEW) {
      return;
    }
    const markCopied = () => {
      setCopied(true);
      setTimeout(() => setCopied(false), COPY_FEEDBACK_MS);
    };
    try {
      navigator.clipboard.writeText(previewText).then(markCopied).catch(() => {
        globalThis.alert('Unable to copy. Select the expression and copy it manually.');
      });
    } catch {
      globalThis.alert('Unable to copy. Select the expression and copy it manually.');
    }
  };
  const bgMain = isDark ? '#1e1e1e' : '#ffffff';
  const bgSection = isDark ? '#161b22' : '#ffffff';
  const textColor = isDark ? '#e6edf3' : '#111827';
  const mutedColor = isDark ? 'rgba(230,237,243,0.75)' : '#6b7280';
  const labelSectionColor = isDark ? '#e6edf3' : '#4b5563';
  const inputBg = isDark ? '#0d1117' : '#ffffff';
  const MINT_BORDER_LIGHT = '#a8e6cf';
  const borderOuter = isDark ? 'rgba(38, 208, 124, 0.35)' : MINT_BORDER_LIGHT;
  const borderInput = isDark ? 'rgba(38, 208, 124, 0.35)' : '#d1d5db';
  const borderSection = isDark ? 'rgba(38, 208, 124, 0.2)' : MINT_BORDER_LIGHT;
  const accentMint = isDark ? 'rgba(38, 208, 124, 0.85)' : '#5cb896';
  const accentMintBorder = isDark ? 'rgba(38, 208, 124, 0.5)' : MINT_BORDER_LIGHT;
  const copyIconColor = isDark ? 'rgba(38, 208, 124, 0.9)' : '#22c55e';
  const containerStyle = {
    background: bgMain,
    border: '1px solid ' + borderOuter,
    borderRadius: '16px',
    padding: '1.25rem',
    margin: '1rem 0',
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
    boxShadow: 'none',
    color: textColor
  };
  const sectionStyle = {
    marginBottom: '0.75rem',
    background: bgSection,
    borderRadius: '12px',
    padding: '0.75rem',
    border: '1px solid ' + borderSection
  };
  const labelStyle = {
    display: 'block',
    fontWeight: 500,
    marginBottom: '0.35rem',
    color: labelSectionColor,
    fontSize: '0.8rem',
    textTransform: 'uppercase',
    letterSpacing: '0.5px'
  };
  const controlStyle = {
    width: '100%',
    padding: '0.5rem 0.75rem',
    border: '1px solid ' + borderInput,
    borderRadius: '8px',
    fontSize: '0.85rem',
    background: inputBg,
    color: textColor,
    cursor: 'pointer',
    boxSizing: 'border-box',
    outline: 'none'
  };
  const rowWrapStyle = {
    marginBottom: '0.6rem'
  };
  const rowStyle = {
    display: 'flex',
    gap: '0.4rem',
    alignItems: 'center',
    flexWrap: 'wrap'
  };
  const rowDescriptionStyle = {
    margin: '0.25rem 0 0',
    paddingLeft: '0.15rem',
    fontSize: '0.75rem',
    lineHeight: 1.4,
    color: mutedColor,
    fontStyle: 'italic'
  };
  const previewBoxStyle = {
    position: 'relative',
    background: inputBg,
    border: '1px solid ' + borderSection,
    borderRadius: '8px',
    padding: '0.75rem',
    paddingRight: '2.75rem',
    marginTop: '0.5rem'
  };
  const preStyle = {
    margin: 0,
    whiteSpace: 'pre-wrap',
    wordBreak: 'break-all',
    fontFamily: 'Monaco, Menlo, Ubuntu Mono, monospace',
    fontSize: '0.8rem',
    lineHeight: 1.4,
    color: isDark ? textColor : '#374151'
  };
  const btnPrimaryOutline = {
    padding: '0.25rem 0.5rem',
    fontSize: '0.75rem',
    fontWeight: 500,
    borderRadius: '8px',
    cursor: 'pointer',
    border: '1px solid ' + accentMintBorder,
    background: 'transparent',
    color: accentMint
  };
  const btnDangerOutline = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    padding: '0.35rem',
    minWidth: '2rem',
    minHeight: '2rem',
    borderRadius: '8px',
    cursor: 'pointer',
    border: '1px solid rgba(220, 53, 69, 0.45)',
    background: 'transparent',
    color: '#f85149'
  };
  const btnCopyStyle = {
    position: 'absolute',
    top: '0.5rem',
    right: '0.5rem',
    background: 'transparent',
    border: 'none',
    color: copyIconColor,
    cursor: 'pointer',
    padding: '0.25rem',
    fontSize: '0.85rem'
  };
  const renderEnumSelect = (row, fieldDef) => {
    const key = fieldDef.enumKey;
    const opts = ENUMS[key] || [];
    return <select aria-label="Filter value" className="pfb-value pfb-control" style={{
      ...controlStyle,
      flex: 1,
      minWidth: '140px'
    }} value={row.value} disabled={!row.operator} onChange={e => updateRow(row.id, {
      value: e.target.value
    })}>
        <option value="">Select value…</option>
        {opts.map(o => <option key={o.value} value={o.value}>
            {o.label}
          </option>)}
      </select>;
  };
  const renderBooleanSelect = row => <select aria-label="Filter value" className="pfb-value pfb-control" style={{
    ...controlStyle,
    flex: 1,
    minWidth: '100px'
  }} value={row.value} disabled={!row.operator} onChange={e => updateRow(row.id, {
    value: e.target.value
  })}>
      <option value="">Select value…</option>
      <option value="true">true</option>
      <option value="false">false</option>
    </select>;
  const renderValueControl = (row, fieldDef) => {
    const kind = valueKindForRow(fieldDef);
    if (kind === 'boolean') {
      return renderBooleanSelect(row);
    }
    if (kind === 'enum') {
      return renderEnumSelect(row, fieldDef);
    }
    const placeholder = fieldDef && TIMESTAMP_FIELDS.has(fieldDef.value) ? 'e.g. -168h or numeric' : 'Value';
    return <input type="text" aria-label="Filter value" className="pfb-value pfb-control" style={{
      ...controlStyle,
      flex: 1,
      minWidth: '120px'
    }} placeholder={placeholder} value={row.value} disabled={!row.operator} onChange={e => updateRow(row.id, {
      value: e.target.value
    })} />;
  };
  const renderRow = row => {
    const fieldDef = resolveFieldDef(row.field);
    const opList = operatorsForFieldDef(fieldDef);
    const showRemove = rows.length > 1;
    return <div key={row.id} style={rowWrapStyle}>
        <div style={rowStyle}>
          <select aria-label="Filter attribute" className="pfb-control" style={{
      ...controlStyle,
      flex: '1.1 1 120px',
      minWidth: '140px'
    }} value={row.field} onChange={e => handleFieldChange(row.id, e.target.value)}>
            <option value="">Select attribute…</option>
            {FIELDS.map(f => <option key={f.value} value={f.value} title={f.description}>
                {f.label}
              </option>)}
          </select>

          <select aria-label="Filter operator" className="pfb-control" style={{
      ...controlStyle,
      flex: '0.9 1 100px',
      minWidth: '120px'
    }} value={row.operator} onChange={e => updateRow(row.id, {
      operator: e.target.value
    })} disabled={!row.field}>
            <option value="">Select operator…</option>
            {opList.map(op => <option key={op.value} value={op.value}>
                {op.label}
              </option>)}
          </select>

          {renderValueControl(row, fieldDef)}

          {showRemove ? <button type="button" style={btnDangerOutline} onClick={() => handleRemoveRow(row.id)} aria-label="Remove condition" title="Remove condition">
              <svg xmlns="http://www.w3.org/2000/svg" width={DELETE_ICON_PX} height={DELETE_ICON_PX} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                <polyline points="3 6 5 6 21 6" />
                <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
                <line x1="10" y1="11" x2="10" y2="17" />
                <line x1="14" y1="11" x2="14" y2="17" />
              </svg>
            </button> : null}
        </div>
        {fieldDef && fieldDef.description ? <div style={rowDescriptionStyle}>{fieldDef.description}</div> : null}
      </div>;
  };
  const pfbThemeClass = isDark ? 'pfb-dark' : 'pfb-light';
  return <div className={'not-prose project-filter-builder ' + pfbThemeClass} style={containerStyle}>
      <style>
        {`
          .project-filter-builder.pfb-light input.pfb-value::placeholder {
            color: #9ca3af;
          }
          .project-filter-builder.pfb-dark input.pfb-value::placeholder {
            color: rgba(230, 237, 243, 0.45);
          }
          .project-filter-builder.pfb-light select option {
            color: #111827;
            background: #f3f4f6;
          }
          .project-filter-builder.pfb-dark select option {
            color: #e6edf3;
            background: #161b22;
          }
          .project-filter-builder.pfb-light .pfb-control:focus {
            border-color: #a8e6cf;
            box-shadow: 0 0 0 2px rgba(168, 230, 207, 0.45);
          }
          .project-filter-builder.pfb-dark .pfb-control:focus {
            border-color: rgba(38, 208, 124, 0.55);
            box-shadow: 0 0 0 2px rgba(38, 208, 124, 0.2);
          }
          .project-filter-builder .pfb-control:disabled {
            opacity: 0.55;
            cursor: not-allowed;
          }
        `}
      </style>
      <div style={{
    marginBottom: '1rem'
  }}>
        <h3 style={{
    color: textColor,
    margin: '0 0 0.25rem',
    fontWeight: 600,
    fontSize: '1.1rem'
  }}>
          Project Filter Builder
        </h3>
        <p style={{
    color: mutedColor,
    margin: 0,
    fontSize: '0.85rem',
    lineHeight: 1.4
  }}>
          Construct advanced project filters using the API-style syntax.
        </p>
      </div>

      <div style={sectionStyle}>
        <div style={{
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginBottom: '0.5rem',
    flexWrap: 'wrap',
    gap: '0.5rem'
  }}>
          <span style={{
    ...labelStyle,
    marginBottom: 0
  }}>Build your filter</span>
          <button type="button" style={btnPrimaryOutline} onClick={handleAddRow}>
            + Add condition
          </button>
        </div>
        <div>{rows.map(row => renderRow(row))}</div>
      </div>

      <div style={sectionStyle}>
        <span style={labelStyle}>Generated filter expression</span>
        <div style={previewBoxStyle}>
          <pre style={preStyle}>{previewText}</pre>
          <button type="button" style={btnCopyStyle} onClick={handleCopy} aria-label={copied ? 'Copied' : 'Copy filter expression to clipboard'} title="Copy to clipboard">
            {copied ? 'Copied' : 'Copy'}
          </button>
        </div>
        <p style={{
    fontSize: '0.8rem',
    color: mutedColor,
    margin: '0.5rem 0 0'
  }}>
          Copy this expression and paste it into the <strong style={{
    color: textColor
  }}>Advanced Filter</strong> box in
          the Projects UI.
        </p>
      </div>
    </div>;
};

export const YamlTable = ({children, data: propData, content}) => {
  const KV_RE = /^([A-Za-z][A-Za-z0-9_()/#\s-]+?):\s*(.+)$/;
  const INLINE_MD_RE = /(\[([^\]]+)\]\(([^)]+)\))|(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*]+)\*)/g;
  const YES_RE = /^-yes-$/i;
  const NO_RE = /^-no-$/i;
  const LIMITED_RE = /^-(limited|partial)-$/i;
  const NA_RE = /^-(na|none)-$/i;
  const NA2_RE = /^-na2-$/i;
  const SIMPLE_TAG_RE = /(<br\s*\/?>)|(<p\s*\/?>)|(-note-)|(-warning-)/gi;
  const tryParseKV = trimmed => {
    const m = KV_RE.exec(trimmed);
    return m ? {
      key: m[1],
      value: m[2].trim()
    } : null;
  };
  const registerKey = (key, seenKeys, orderedKeys) => {
    if (!seenKeys.has(key)) {
      orderedKeys.push(key);
      seenKeys.add(key);
    }
  };
  const flushEntry = (currentEntry, entries) => {
    if (Object.keys(currentEntry).length > 0) entries.push(currentEntry);
  };
  const parseDashPrefixed = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed.startsWith('- ')) {
        if (inEntry) entries.push(currentEntry);
        currentEntry = {};
        inEntry = true;
        const kv = tryParseKV(trimmed.substring(2).trim());
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      } else if (inEntry && trimmed !== '') {
        const kv = tryParseKV(trimmed);
        if (kv) {
          registerKey(kv.key, seenKeys, orderedKeys);
          currentEntry[kv.key] = kv.value;
        }
      }
    }
    flushEntry(currentEntry, entries);
  };
  const parseBlankSeparated = (lines, entries, orderedKeys, seenKeys) => {
    let currentEntry = {};
    let inEntry = false;
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed === '') {
        if (inEntry) {
          flushEntry(currentEntry, entries);
          currentEntry = {};
          inEntry = false;
        }
        continue;
      }
      const kv = tryParseKV(trimmed);
      if (!kv) continue;
      const isNewEntry = !line.startsWith(' ') && !line.startsWith('\t');
      if (isNewEntry && inEntry && Object.keys(currentEntry).length > 0) {
        entries.push(currentEntry);
        currentEntry = {};
      }
      registerKey(kv.key, seenKeys, orderedKeys);
      currentEntry[kv.key] = kv.value;
      inEntry = true;
    }
    flushEntry(currentEntry, entries);
  };
  const normalizeEntries = (entries, orderedKeys) => entries.map(entry => {
    const filled = {};
    for (const key of orderedKeys) filled[key] = entry[key] || '';
    return filled;
  });
  const parseYamlTableContent = contentStr => {
    if (!contentStr) return [];
    const entries = [];
    const orderedKeys = [];
    const seenKeys = new Set();
    const lines = contentStr.split('\n');
    if (lines.some(line => line.trim().startsWith('- '))) {
      parseDashPrefixed(lines, entries, orderedKeys, seenKeys);
    } else {
      parseBlankSeparated(lines, entries, orderedKeys, seenKeys);
    }
    return normalizeEntries(entries, orderedKeys);
  };
  const processText = text => {
    if (!text) return text;
    const parts = [];
    let keyIndex = 0;
    let lastIndex = 0;
    let match;
    while ((match = INLINE_MD_RE.exec(text)) !== null) {
      if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index));
      if (match[1]) {
        parts.push(<a key={keyIndex++} href={match[3]}>{match[2]}</a>);
      } else if (match[4]) {
        parts.push(<code key={keyIndex++}>{match[5]}</code>);
      } else if (match[6]) {
        parts.push(<strong key={keyIndex++}>{match[7]}</strong>);
      } else if (match[8]) {
        parts.push(<em key={keyIndex++}>{match[9]}</em>);
      }
      lastIndex = match.index + match[0].length;
    }
    if (lastIndex < text.length) parts.push(text.slice(lastIndex));
    if (parts.length === 0) return text;
    const keyRef = {
      current: keyIndex
    };
    return expandHtmlTags(parts, keyRef);
  };
  const processBadges = text => {
    if (!text || typeof text !== 'string') return text;
    if (YES_RE.test(text)) return <span className="yt-badge-yes" role="img" aria-label="Supported" title="Supported">✓</span>;
    if (NO_RE.test(text)) return <span className="yt-badge-no" role="img" aria-label="Not supported" title="Not supported">✗</span>;
    if (LIMITED_RE.test(text)) return <span className="yt-badge-limited" role="img" aria-label="Partially supported" title="Partially supported">◐</span>;
    if (NA_RE.test(text) || NA2_RE.test(text)) return <span className="yt-sr-only" title="Not applicable">Not applicable</span>;
    return processText(text);
  };
  const cellClassName = text => {
    if (!text || typeof text !== 'string') return undefined;
    if (NA_RE.test(text)) return 'yt-cell-na';
    if (NA2_RE.test(text)) return 'yt-cell-na2';
    return undefined;
  };
  const expandSimpleTags = (str, keyRef) => {
    const result = [];
    let last = 0;
    SIMPLE_TAG_RE.lastIndex = 0;
    let m;
    while ((m = SIMPLE_TAG_RE.exec(str)) !== null) {
      if (m.index > last) result.push(str.slice(last, m.index));
      if (m[1]) {
        result.push(<br key={keyRef.current++} />);
      } else if (m[2]) {
        result.push(<br key={keyRef.current++} />, <br key={keyRef.current++} />);
      } else if (m[3]) {
        result.push(<span key={keyRef.current++} className="yt-badge-note" style={{
          fontWeight: 600
        }}>Note: </span>);
      } else if (m[4]) {
        result.push(<span key={keyRef.current++} className="yt-badge-warning" style={{
          fontWeight: 600
        }}>Warning: </span>);
      }
      last = m.index + m[0].length;
    }
    if (last < str.length) result.push(str.slice(last));
    return result;
  };
  const expandHtmlTags = (chunks, keyRef) => {
    const out = [];
    for (const chunk of chunks) {
      if (typeof chunk === 'string') {
        out.push(...expandSimpleTags(chunk, keyRef));
      } else {
        out.push(chunk);
      }
    }
    return out;
  };
  const extractText = node => {
    if (node === null || node === undefined) return '';
    if (typeof node === 'string') return node;
    if (typeof node === 'number') return String(node);
    if (typeof node === 'boolean') return '';
    if (Array.isArray(node)) return node.map(extractText).join('');
    if (node && typeof node === 'object' && node.type) {
      const props = node.props || ({});
      if (typeof props.children === 'string') return props.children;
      if (props.children) return extractText(props.children);
      return '';
    }
    return String(node || '');
  };
  const [mounted, setMounted] = useState(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  const data = useMemo(() => {
    if (propData) return propData;
    if (content && typeof content === 'string') return parseYamlTableContent(content);
    if (!children) return [];
    if (typeof children === 'string') return parseYamlTableContent(children);
    const childrenArray = Array.isArray(children) ? children : [children];
    return parseYamlTableContent(childrenArray.map(extractText).join('').trim());
  }, [children, propData, content]);
  const columns = useMemo(() => {
    if (!data || data.length === 0) return [];
    const firstRow = data[0];
    if (!firstRow || typeof firstRow !== 'object') return [];
    return Object.keys(firstRow);
  }, [data]);
  if (!mounted) return null;
  if (!data || data.length === 0) return null;
  const rowKey = row => columns.map(c => row[c] || '').join('|');
  return <table>
      <thead>
        <tr>
          {columns.map(col => <th key={col}>{col.replaceAll('_', ' ')}</th>)}
        </tr>
      </thead>
      <tbody>
        {data.map(row => <tr key={rowKey(row)}>
            {columns.map(col => <td key={col} className={cellClassName(row[col])}>{processBadges(row[col])}</td>)}
          </tr>)}
      </tbody>
    </table>;
};

Filters enable targeted queries based on attributes such as severity, package ecosystems, dependency resolution, and platform source.

This guide explains how filters work, how to apply and combine them effectively, and provides practical examples to support triage, audit, and reporting workflows across large codebases.

## Access project filters

Perform the following steps to apply filters to the project list.

1. Select **Projects** from the left sidebar.
2. Filter your projects using the list of available filters in the filter bar.
3. Toggle the **Advanced** option in the filter bar to apply API-style filters.

You can combine multiple filters to create more specific searches and narrow down the project list based on multiple criteria.

## How filters work

Each filter consists of three parts.

* **Field**: The attribute you want to filter (for example, `Package Count` and `Platform Source`).
* **Operator**: The comparison logic (for example, `equals`, `greater than`, and `in`).
* **Value**: The target value to evaluate (for example, `npm` and `100`).

Project filters use standard comparison operators to evaluate criteria. See [Filter operators](/developers-api/rest-api/using-the-rest-api/filters#operators) for detailed information about available operators and their usage.

When you apply multiple filters, the system combines them using logical AND operations across filters for different fields and logical OR operations across filters for the same field.

For example:

* **Filter 1**: `Package Ecosystems contains: npm`
* **Filter 2**: `Platform Source in: GitHub`
* **Filter 3**: `Package Count: greater than 1`
* **Filter 4**: `Reachability Analysis Status greater than or equal to: 90%`

<img src="https://mintcdn.com/endorlabs-b4795f4f/deISr2DfATZg293W/images/best-practices/project-filters/project-filters/multiple-filters.webp?fit=max&auto=format&n=deISr2DfATZg293W&q=85&s=4d9ea0df181c8c32c34e3523d29057be" alt="Multiple filters" style={{width: '70%'}} width="2556" height="840" data-path="images/best-practices/project-filters/project-filters/multiple-filters.webp" />

This combination returns only projects that use npm packages, are from GitHub, have more than one package, and have a reachability analysis status of at least 90%.

## Filter implementation techniques

You can use the following filter types to manage your projects effectively.

* [Preset filters](#preset-filters): Use predefined UI-based filters to quickly segment projects by common attributes.
* [Filter projects using API](#filter-projects-using-api): Use advanced syntax for complex queries and logical combinations.

### Preset filters

The following examples demonstrate how to apply preset filters for common project scenarios.

#### Filter projects by custom tags

Use custom tags to filter projects based on environment or predefined labels assigned during project initialization or scan configuration.

For example, to view only projects related to SAST, use the `Custom Tags contains: sast` filter.

<img src="https://mintcdn.com/endorlabs-b4795f4f/deISr2DfATZg293W/images/best-practices/project-filters/project-filters/filter-custom-tags.webp?fit=max&auto=format&n=deISr2DfATZg293W&q=85&s=1580b27a6908b34830c83b56fc1d9f9e" alt="filter by custom tags" style={{width: '70%'}} width="2556" height="598" data-path="images/best-practices/project-filters/project-filters/filter-custom-tags.webp" />

#### Filter projects by findings severity

Prioritize remediation efforts by filtering projects based on the severity of security findings. You can select from **Critical (C)**, **High (H)**, **Medium (M)**, or **Low (L)** severity filters to target different priority levels.

For example, to identify projects with critical findings, select the **C** filter.

<img src="https://mintcdn.com/endorlabs-b4795f4f/deISr2DfATZg293W/images/best-practices/project-filters/project-filters/filter-finding-severity.webp?fit=max&auto=format&n=deISr2DfATZg293W&q=85&s=e6b5624341c4b592ae59dbafd0687811" alt="filter by severity" style={{width: '70%'}} width="2564" height="868" data-path="images/best-practices/project-filters/project-filters/filter-finding-severity.webp" />

#### Filter projects by package ecosystem

Use package ecosystem filters to segment projects by language or package manager. Apply targeted policies, such as stricter vulnerability thresholds for JavaScript or license compliance checks for Java.

For example, to focus on PHP projects for a security assessment, use the `Package Ecosystems contains: Packagist` filter.

<img src="https://mintcdn.com/endorlabs-b4795f4f/deISr2DfATZg293W/images/best-practices/project-filters/project-filters/filter-package-ecosystem.webp?fit=max&auto=format&n=deISr2DfATZg293W&q=85&s=eb98e9241ff0b1add02dfff7692d0dd2" alt="filter by package ecosystem" style={{width: '70%'}} width="2544" height="820" data-path="images/best-practices/project-filters/project-filters/filter-package-ecosystem.webp" />

#### Filter projects by source platform

Use platform source filters to segment projects by their source platform and correlate findings with platform-native security tools like GitHub's Dependabot alerts or GitLab's vulnerability scanning.

For example, to identify projects analyzed from GitLab, use the `Platform Source in: GitLab` filter.

<img src="https://mintcdn.com/endorlabs-b4795f4f/deISr2DfATZg293W/images/best-practices/project-filters/project-filters/filter-source-platform.webp?fit=max&auto=format&n=deISr2DfATZg293W&q=85&s=adf9b6840ca10c6238a722258b21fa70" alt="filter by source platform" style={{width: '70%'}} width="2542" height="664" data-path="images/best-practices/project-filters/project-filters/filter-source-platform.webp" />

#### Filter projects by dependency resolution quality

Use dependency resolution status to identify projects with resolution issues that impact security analysis accuracy. You can filter by a single value or a range of percentages.

For example, to identify projects with poor dependency resolution, use the **Range** filter to find projects with `Dependency Resolution Status greater than 0% and less than 50%`.

<img src="https://mintcdn.com/endorlabs-b4795f4f/deISr2DfATZg293W/images/best-practices/project-filters/project-filters/filter-dependency-resolution-status.webp?fit=max&auto=format&n=deISr2DfATZg293W&q=85&s=babbe9f8df560e521d3d4c0eedd4c55f" alt="filter by resolution" style={{width: '70%'}} width="2560" height="756" data-path="images/best-practices/project-filters/project-filters/filter-dependency-resolution-status.webp" />

#### Filter projects by scan timestamp

Use last scanned filters to identify projects with stale security data that require fresh scans for current security posture. You can select from predefined time ranges or use the calendar to select a specific date.

For example, to identify projects scanned within the last 24 hours, use the `Last Scanned: Last Day` filter.

<img src="https://mintcdn.com/endorlabs-b4795f4f/deISr2DfATZg293W/images/best-practices/project-filters/project-filters/filter-last-scanned.webp?fit=max&auto=format&n=deISr2DfATZg293W&q=85&s=2183af616f23f032e7f76fbd7dd33ec7" alt="filter by last scanned" style={{width: '70%'}} width="2544" height="956" data-path="images/best-practices/project-filters/project-filters/filter-last-scanned.webp" />

#### Filter projects by complexity

Identify projects based on their size and complexity, which may require different levels of security attention and resources.

For example, to focus on large projects with extensive dependency trees, use the `Package Count: greater than 100` filter.

<img src="https://mintcdn.com/endorlabs-b4795f4f/deISr2DfATZg293W/images/best-practices/project-filters/project-filters/filter-complexity.webp?fit=max&auto=format&n=deISr2DfATZg293W&q=85&s=e6bb01e84ebd182478ae2061d93045ff" alt="filter by complexity" style={{width: '70%'}} width="2564" height="468" data-path="images/best-practices/project-filters/project-filters/filter-complexity.webp" />

#### Filter projects by reachability analysis status

Use reachability analysis status to identify projects based on the success rate of call graph generation and reachability analysis. You can filter by a single value or a range of percentages.

For example, to identify projects with successful reachability analysis, use the `Reachability Analysis Status greater than or equal to: 90%` filter.

<img src="https://mintcdn.com/endorlabs-b4795f4f/deISr2DfATZg293W/images/best-practices/project-filters/project-filters/filter-reachability-analysis-status.webp?fit=max&auto=format&n=deISr2DfATZg293W&q=85&s=8d917849c9b83aaa78e44e9b48938a7f" alt="filter by reachability status" style={{width: '70%'}} width="2546" height="924" data-path="images/best-practices/project-filters/project-filters/filter-reachability-analysis-status.webp" />

### Filter projects using API

For complex queries, use the advanced filter syntax to combine multiple attributes and apply logical operators. Use the [Project filter builder](#interactive-filter-builder) to assemble conditions interactively.

The following table lists the available attributes for project filters.

<YamlTable>
  {`


    - Attribute: Project UUID
    Description: Filters projects by their unique identifier.

    - Attribute: Name
    Description: Filters projects by the display name of the project.

    - Attribute: Custom Tags
    Description: Filters projects by custom tags set during project setup or scan configuration.

    - Attribute: Last Scanned
    Description: Filters projects based on the timestamp of the last successful scan.

    - Attribute: Package Count
    Description: Filters projects by the total number of packages resolved in the project.

    - Attribute: Package Ecosystems
    Description: Filters projects based on the language-specific package ecosystems they use.

    - Attribute: Dependency Resolution Status
    Description: Filters projects by the percentage of resolved dependencies in the project.

    - Attribute: Reachability Analysis Status
    Description: Filters projects based on the success rate of reachability analysis through call graph generation.

    - Attribute: Critical Findings Count
    Description: Filters projects based on the number of critical-severity findings in the project.

    - Attribute: High Findings Count
    Description: Filters projects based on the number of high-severity findings in the project.

    - Attribute: Medium Findings Count
    Description: Filters based on the number of medium-severity findings in the project.

    - Attribute: Low Findings Count
    Description: Filters projects based on the number of low-severity findings in the project.

    - Attribute: Dismissed Findings Count
    Description: Filters projects by the number of findings that users dismissed. Useful for reviewing triage quality.

    - Attribute: Total Findings Count
    Description: Filters projects based on the total number of findings detected in a project.

    - Attribute: Platform Source
    Description: Filters projects based on source platform and helps narrow results by version control origin such as GitHub, GitLab, or Bitbucket.

    - Attribute: Automated Scan Enabled
    Description: Filters projects by whether automated scanning is enabled.

    - Attribute: Total Vulnerabilities
    Description: Filters projects by the total number of vulnerabilities detected across all severity levels.

    - Attribute: Critical Vulnerabilities
    Description: Filters projects by the number of critical-severity vulnerabilities detected.

    - Attribute: High Vulnerabilities
    Description: Filters projects by the number of high-severity vulnerabilities detected.

    - Attribute: Medium Vulnerabilities
    Description: Filters projects by the number of medium-severity vulnerabilities detected.

    - Attribute: Low Vulnerabilities
    Description: Filters projects by the number of low-severity vulnerabilities detected.

    - Attribute: Direct Dependencies
    Description: Filters projects by the count of dependencies declared directly in project manifests.

    - Attribute: Transitive Dependencies
    Description: Filters projects by the count of transitive (indirect) dependencies pulled in through direct dependencies.

    - Attribute: Outdated Dependencies
    Description: Filters projects by the number of dependencies with newer versions available.

    - Attribute: Unmaintained Dependencies
    Description: Filters projects by the number of dependencies that are no longer actively maintained.


    `}
</YamlTable>

#### Interactive filter builder

<ProjectFilterBuilder />

#### API filter use cases

The following examples demonstrate how to combine these attributes for common security and compliance workflows.

<AccordionGroup>
  <Accordion title="Identify high-risk GitHub projects">
    Find GitHub projects with more than 5 critical findings and a reachability analysis status below 80%.

    ```bash theme={null}
    spec.platform_source == PLATFORM_SOURCE_GITHUB and spec.finding_counts.critical > 5 and spec.package_coverage.call_graph_success_rate < 0.8
    ```
  </Accordion>

  <Accordion title="Audit stale projects with risks">
    Identify projects not scanned in the last 7 days that still have outdated dependencies.

    ```bash theme={null}
    spec.last_scanned < now(-168h) and spec.dependency_counts.outdated > 0
    ```
  </Accordion>

  <Accordion title="Identify projects with low dependency resolution quality">
    Find projects where dependency resolution is below 90%, which may indicate incomplete security analysis.

    ```bash theme={null}
    spec.package_coverage.success_rate < 0.9
    ```
  </Accordion>

  <Accordion title="Find large projects with high vulnerability density">
    Identify projects with more than 100 total packages that also have a high number of critical vulnerabilities.

    ```bash theme={null}
    spec.package_coverage.total > 100 and spec.vulnerability_counts.total.critical > 20
    ```
  </Accordion>

  <Accordion title="Audit projects with high triaged findings">
    Find projects where more than 50 findings have been dismissed, useful for auditing triage quality.

    ```bash theme={null}
    spec.finding_counts.dismissed > 50
    ```
  </Accordion>

  <Accordion title="Triage critical vulnerabilities by ecosystem">
    Focus on npm projects with a high number of critical vulnerabilities.

    ```bash theme={null}
    spec.package_coverage.ecosystems contains ECOSYSTEM_NPM and spec.vulnerability_counts.total.critical > 10
    ```
  </Accordion>
</AccordionGroup>
