Skip to content

Give each client their own view

Business plan Admin

A client portal is one published app showing each client a different set of rows: theirs. The app matches the address somebody signed in with against a column naming who each row belongs to.

Three of the four pieces already exist. The fourth is the setting this article covers:

  1. A list holding the rows, with a column naming the client each row is for.
  2. A published app reading from it. See Show collected data in your app.
  3. An access mode that identifies people. See Control who can use your published app.
  4. Who sees which rows.

Steps

  1. Open the app in Build and publish it if you have not already.
  2. In Who can see it, set the access mode to email sign-in or workspace members.
  3. In the Data panel, set your data view to Show rows and tick the columns to expose. Scoping does not appear on a count-only view, which has no rows to scope.
  4. In Who sees which rows, choose the column naming who each row belongs to. The default is Everyone sees every row.
  5. Save. It applies to the next request, with no republish.

Every column is offered, not just email-looking ones, because yours might be called Contact, Owner, Who, or something in your own language. If none of its sample values contains an @, Onplana warns you in amber: a column of company names matches nobody, and every client would get an empty list.

A row is served only when its cell in the chosen column equals the visitor’s sign-in address. Matching ignores case, so Ana@Acme.com matches ana@acme.com. It does not ignore stray whitespace: " ana@acme.com" fails rather than being trimmed into a match, because on a boundary deciding who sees whose data a near-miss should fail.

The count follows the same filter. A client owning three rows in a list of three hundred sees 3, since a count over the whole table would tell every client how much work you do for the others.

SituationWhat happens
No column chosenEvery signed-in visitor sees every row. The old behaviour, correct for a shared dashboard.
Column set, visitor has no identityNothing served. An unidentified caller getting everything is the leak.
Row has no value in that columnServed to nobody. A row belonging to nobody must not read as belonging to everybody.
Row belongs to someone elseNot served. The case the feature exists for.

Changing the access mode does not clear the scoping. Scope an app, later switch it to open or passcode access, and the column stays set with no address to match, so every visitor gets an empty list. Either return it to an identifying sign-in, or set Who sees which rows back to Everyone sees every row.

“Only mine” is a different filter. Scoping matches rows the visitor owns and is your configuration, unchangeable from a request. The mine filter matches rows the visitor created, and is their choice. On a portal your firm creates the rows and the client only reads them, so mine would match nothing. Scoping is the one you want.

Read the CRM directly, no list to maintain

Section titled “Read the CRM directly, no list to maintain”

A list is the right source when the rows exist nowhere else. When what the client should see is already in your CRM, a data view can read it there, so nothing is copied and nothing goes stale.

Steps

  1. In the Data panel, under Or show each client their own CRM records, pick Clients, Contacts or Deals. The view appears the same way a list view does.
  2. Switch it to Show rows and tick the fields to expose. Only fields that are safe to show a client are offered: a company’s name, industry, website and location; a contact’s name, email, phone and title; a deal’s title, stage, value, billing terms, dates and quote lines. Internal notes, sales probability, lost reasons and account owners are not on the list and cannot be ticked.
  3. Publish, and set the access mode to email sign-in or workspace members.

There is no Who sees which rows setting on a CRM view, because there is no choice to make: each visitor is matched by their sign-in address against your Contacts, and sees only the records of the company that contact belongs to. A visitor whose address is not one of your contacts sees nothing, and so does your own preview, unless your address is a contact of one of your clients.

The response has the same shape as a list view, { rows, count, nextCursor }, with each row keyed by the field name. Money arrives as a number, dates as ISO strings, and a deal’s Quote lines as an array of lines with their own fields. A count-only view returns { count }, already scoped to the visitor.

Only a hash of the verified address. The plaintext lives in the signed token issued at sign-in, which is what the row match runs against, so a visitor cannot claim to be somebody else by editing what they send. A token minted for a different app is refused.