Build Brief
WeWeb + Xano 8 hours Beginner–Intermediate

What You Are Building

A workspace contact book (light CRM). Members can add, view, edit, and delete contacts. Contacts have a name, email, phone, company, status, and notes. The list is searchable, filterable, and paginated. All backend logic lives in Xano with proper auth, permission checks, and error handling.

Roles & Access
  • MemberCan list contacts, open detail views, and use the shared add or edit workflow defined for the contact UI.
  • Admin / OwnerCan also permanently delete contacts through the protected Xano delete endpoint.
Core Flows

How this product should work

This should read like a lightweight CRM, with clear list behavior, reusable forms, and backend permissions that actually match what the UI exposes.

Searchable contact list

The list page keeps search, filter, sort, and pagination state aligned with the Xano query params instead of relying on local-only UI tricks.

Detail view

Users open a full contact record, view all fields including notes, and see edit or delete actions based on role.

Shared contact modal

The same component powers add and edit, with duplicate email handling and inline validation feedback.

Protected delete

Delete is reserved for admin or owner roles and always runs through a confirmation-driven cleanup flow.

Features to Build

# Feature Est. Time
1 Xano tables + check_workspace_permission reusable function 1h
2 Xano endpoints: list, get, create, update, delete 1h 30m
3 Contacts list page (search, filter, sort, pagination) 1h 30m
4 Add / Edit contact modal (shared form) 1h 30m
5 Contact detail view (side panel or page) 30m
6 Delete contact (confirmation, admin only) 30m
7 All states + permissions 30m

1. Xano Database Tables

contacts Table

Column Type Rules
id int (auto) PK
workspace_id int FK → workspaces.id, required
full_name text required, max 100 chars
email text optional, valid email format if provided
phone text optional, E.164 format if provided
company text optional, max 100 chars
status enum required, one of: 'lead', 'active', 'inactive', default: 'lead'
notes text optional, max 1000 chars
created_by int FK → users.id, set from auth token
created_at timestamp auto
updated_at timestamp auto

2. Xano Reusable Function

check_workspace_permission

Before building endpoints, extract this reusable function:

Inputs:
  workspace_id (integer, required)
  user_id (integer, required)
  required_role (text, optional) — 'member', 'admin', 'owner'

Logic:
  1. Query workspace_members WHERE workspace_id = input AND user_id = input AND status = 'active'
  2. If not found: RAISE FORBIDDEN "You are not a member of this workspace."
  3. If required_role provided: check role hierarchy — RAISE FORBIDDEN if insufficient
  4. Return member record

Role hierarchy: member < admin < owner

Run checklist: Reusable Function


3. Xano Endpoints

All endpoints follow this structure:

  1. Auth Token — validate as the first precondition
  2. check_workspace_permission — called with workspace_id from the request
  3. Input validation — validate all required fields
  4. Business logic — query/mutate the database
  5. Response — standard format

GET /api/contacts — List Contacts

Auth: Required
Input query params:
  workspace_id (required)
  search (optional) — searches full_name, email, company
  status (optional) — filter by status
  sort (optional) — 'full_name', 'created_at', 'company' (default: created_at DESC)
  page (optional, default: 1)
  per_page (optional, default: 25, max: 100)

Logic:
  1. Auth + workspace permission check (any member)
  2. Build query with filters applied
  3. Return paginated results

Response:
  { "data": [...], "total": 143, "page": 1, "per_page": 25 }

GET /api/contacts/{id} — Get Single Contact

Auth: Required
Logic:
  1. Auth + workspace permission (any member)
  2. Verify contact belongs to workspace (NOT_FOUND if not)
  3. Return contact

POST /api/contacts — Create Contact

Auth: Required
Body: { workspace_id, full_name, email, phone, company, status, notes }
Validation (in order):
  1. full_name: required, max 100 chars
  2. email: valid format if provided
  3. phone: E.164 format if provided
  4. status: one of valid values
  5. Workspace permission (any member)
  6. No duplicate email in same workspace (if email provided)
Logic: Insert contact, set created_by from auth token
Response: { "data": { contact } }
Error codes: VALIDATION_ERROR, DUPLICATE, FORBIDDEN

PUT /api/contacts/{id} — Update Contact

Auth: Required
Same validation as create (except workspace check — verify contact ownership)
Logic: Update contact, set updated_at
Response: { "data": { contact } }

DELETE /api/contacts/{id} — Delete Contact

Auth: Required
Permission: admin or owner role only
Logic: Verify contact in workspace, delete record
Response: { "message": "Contact deleted." }
Error codes: FORBIDDEN, NOT_FOUND

Run checklist: New API Endpoint (Xano) for each endpoint


4. Contacts List Page

URL: /workspace/{id}/contacts

Requirements

HEADER
[ ] "Contacts" title + "Add Contact" button (all members can add)

SEARCH & FILTERS
[ ] Search: searches full_name, email, company — debounced 300ms — sent to Xano
[ ] Filter: Status dropdown (All / Lead / Active / Inactive)
[ ] Active filter chips shown with X to remove
[ ] "Clear all" button when any filter is active
[ ] Filter/search state in URL params: ?search=acme&status=active

SORT
[ ] Sort dropdown or column headers: Name A–Z / Name Z–A / Newest / Oldest
[ ] Sort state in URL: ?sort=full_name&order=asc

TABLE COLUMNS
[ ] Full Name · Company · Email · Phone · Status badge · Actions

STATUS BADGES
[ ] lead=blue, active=green, inactive=grey

PAGINATION
[ ] 25 per page, "Showing X–Y of Z contacts"
[ ] Page in URL: ?page=2
[ ] Pagination resets to 1 when search/filter changes

STATES
[ ] Loading: skeleton rows
[ ] Empty (no contacts): "No contacts yet." + "Add your first contact" button
[ ] Empty (filter/search): "No contacts match your search." + clear button
[ ] Error: "Could not load contacts." + retry button

Run checklist: WeWeb Page Build · Search, Filters & Pagination


5. Contact Detail Panel / Page

A side panel or separate page showing the full contact details.







6. Add / Edit Contact Modal

One shared WeWeb component used for both Add and Edit.

Form Fields

Field Type Rules
Full Name text Required, max 100 chars
Email email Optional, valid format
Phone tel + country code Optional, E.164 stored
Company text Optional, max 100 chars
Status dropdown Required, default: Lead
Notes textarea Optional, max 1000 chars, show character count

Behavior

ADD MODE: title "Add Contact", submit "Add Contact"
EDIT MODE: title "Edit Contact", pre-filled fields, submit "Save Changes"

BOTH:
[ ] Duplicate email error shown below email field (not a toast)
[ ] Submit button: loading state, disabled during request
[ ] X + Escape close modal (confirm if dirty)
[ ] On success: toast notification, list refreshes
[ ] On API error: red banner above submit button

Run checklist: Add/Edit Consistency · Modals & Dialogs


7. Delete Contact



Title: "Delete Contact?"

Body: "'[Full Name]' will be permanently deleted."

Buttons: Cancel · "Delete Contact" (red)



Run checklist: Delete & Destructive Actions


What You Should NOT Do

× Put permission logic in WeWeb — check_workspace_permission runs in Xano
× Filter contacts client-side — all search/filter sent to Xano
× Use separate Add and Edit components
× Let DELETE endpoint succeed for a member-role user
× Skip the phone format — store E.164 even though display is formatted
× Show delete button to member-role users
× Return 500 for a DUPLICATE email — return a specific error code

Checklists to Run (in order)















Done When