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

# Example: Contact Directory

> Searchable contact directory demonstrating contains text search, multi_select tag filtering, and email/url field types.

A searchable contact directory demonstrating text search with `contains`, `multi_select` tag filtering, and `email`/`url` field types.

## Schema

**Entity:** Contacts — key: `contacts`

| Field     | Type          | Notes                                |
| --------- | ------------- | ------------------------------------ |
| `name`    | text          | required                             |
| `email`   | email         | required                             |
| `phone`   | text          |                                      |
| `company` | text          |                                      |
| `role`    | text          |                                      |
| `website` | url           |                                      |
| `tags`    | multi\_select | client / vendor / partner / internal |
| `notes`   | textarea      |                                      |

## Key Patterns

### Text Search with Debounce

```javascript theme={null}
let searchTimer;
document.getElementById('search').addEventListener('input', () => {
  clearTimeout(searchTimer);
  searchTimer = setTimeout(loadContacts, 300);
});
```

Debouncing prevents a request on every keystroke. 300 ms is a good default.

### Filter by Tag

```javascript theme={null}
const result = await sdk.listRecords('contacts', {
  filter: { tags: { contains: 'vendor' } },
  sort: 'name:asc',
});
```

`contains` on a `multi_select` field matches any record where the array includes the given value.

### Combined Search + Tag Filter

```javascript theme={null}
const filter = {};
if (query) filter.name = { contains: query };
if (tag) filter.tags = { contains: tag };

const result = await sdk.listRecords('contacts', {
  filter: Object.keys(filter).length ? filter : undefined,
  sort: 'name:asc',
});
```

Multiple filter conditions use AND logic — both must match.

### Reading multi\_select Values

```javascript theme={null}
// c.tags is an array of strings, or null/undefined if empty
const selectedTags = Array.isArray(c.tags) ? c.tags : [];
```

### Restoring Checkboxes on Edit

```javascript theme={null}
document.querySelectorAll('input[name="tags"]').forEach(cb => {
  cb.checked = selectedTags.includes(cb.value);
});
```

### Null-Safe HTML Escaping

Always guard `escapeHtml` against `null`/`undefined` values — optional fields may be absent on older records:

```javascript theme={null}
function escapeHtml(str) {
  if (str == null) return '';
  const div = document.createElement('div');
  div.textContent = String(str);
  return div.innerHTML;
}
```
