Reading Data and the Signed-In User in a Dash App
A Dash app can read your PlaidCloud data — and it does so as the person viewing it, exactly like a server Panel app. When someone opens the app, PlaidCloud signs them in with the same single sign-on the rest of the platform uses, and every call your app makes runs with that viewer’s own permissions. There is no shared service account, and the app can never see data the viewer couldn’t see themselves.
The helpers live in plaidcloud_dash, part of the Dash base image:
from plaidcloud_dash import current_user, get_connectionConnect to PlaidCloud
Section titled “Connect to PlaidCloud”Call get_connection() from inside a Dash callback, naming the project whose data you want to read:
from plaidcloud_dash import get_connection
conn = get_connection(project_id="<your-project-id>")That’s all the setup you need. You do not supply a token, username, or password — get_connection() authenticates as the signed-in viewer automatically, using the access token from their current session. Find your project’s id in the project’s URL or its settings page.
Call it from inside a callback.
get_connection()reads the viewer’s token from the current request’s Flask session, which is only available while a callback is handling that viewer’s request. If you push a blocking call onto a background thread, a raw thread pool won’t carry the session — read the token inside the callback and pass it in explicitly instead. Callingget_connection()outside a request (or with authentication disabled) raises aRuntimeErrorrather than silently returning a connection with no identity behind it.
Name your project in code. There is no project setting in the publish dialog — a Dash app reads whichever project you name in
get_connection(project_id=...), the same as a server Panel app’sPlaidConnection(project_id=...)— see Reading Data and the Signed-In User. A bareget_connection()with noproject_idis fine for an app that never touches project data (one that only callscurrent_user(), for example); call a project-scoped operation on that connection without a real, accessible project id and it raisesProject Id has not been set. (To read from more than one project, open a connection per project.)
Read a Table
Section titled “Read a Table”With a connection, pull a table into a pandas DataFrame by name:
df = conn.get_dataframe("Sales Ledger")Because the connection is the viewer, this returns only the rows the viewer is allowed to see, and raises a permission error if they have no access to the project at all.
Read tables by name, not by writing SQL. On a project where an administrator has turned on Row Access, reading by name applies each viewer’s row grants, while a query your app composes itself is declined for anyone who is not a project Architect — there is no single table for the grants to filter. Reading by name keeps your app working either way.
Read in a Callback, Never at Import or Process Scope
Section titled “Read in a Callback, Never at Import or Process Scope”One process serves every viewer. Getting data isolation right comes down to two rules:
-
Never open a connection or read a table at module scope (import time). Your
app.pyand any modules it imports run once, when the container starts — before anyone has signed in. Aget_connection()orPlaidConnection()there has no viewer to authenticate as and fails the app on boot (this is the most common cause of an “app didn’t start” crash loop). Keep every read inside a callback, where a viewer’s request — and their session token — is present. -
Never cache a per-viewer result at module or process scope. The threaded gunicorn worker shares one Python process across all concurrent viewers, so a DataFrame you stash in a module-level variable, a global dict, or an
lru_cacheis visible to every other viewer — one person’s rows leak into another’s view. Compute per-viewer data inside the callback and let it go out of scope when the callback returns:# WRONG — a module/process-global cache is shared across all viewers_CACHE = {}@app.callback(...)def _load(n):viewer = current_user()if viewer not in _CACHE: # still process-global; leaks under load_CACHE[viewer] = get_connection(project_id="…").get_dataframe("Sales")return _CACHE[viewer].to_dict("records")# RIGHT — read inside the callback, no shared state@app.callback(...)def _load(n):conn = get_connection(project_id="…") # this viewer, this requestreturn conn.get_dataframe("Sales").to_dict("records")
Shared Reference Data
Section titled “Shared Reference Data”If several viewers genuinely need the same reference data — a lookup table, a product list — don’t try to share it through a process-level cache. Grant those tables to the app’s users through PlaidCloud’s normal Row Access / project permissions, and let each viewer read them under their own get_connection(). Everyone gets the same reference rows because they’re all granted the same access — not because one viewer’s read is reused for another.
Identify the Viewer
Section titled “Identify the Viewer”To tailor what the app shows to who is looking at it — a personalized greeting, hiding a tab, or row-level security — call current_user():
from plaidcloud_dash import current_user
viewer = current_user() # the viewer's identifiercurrent_user() returns the signed-in viewer’s identifier — their username, falling back to their email and then their account ID if no username claim is present — taken from their verified sign-in, so you can trust it as their identity. It never raises; called with authentication disabled or outside a request, it returns an empty string.
Row-Level Security
Section titled “Row-Level Security”Combine the two: read the viewer, then filter every query by them. For example, to show each regional manager only their own region:
from dash import Input, Outputfrom plaidcloud_dash import current_user, get_connection
@app.callback(Output("sales-table", "children"), Input("load-btn", "n_clicks"))def _load_my_region(n_clicks): viewer = current_user() conn = get_connection(project_id="<your-project-id>")
sales = conn.get_dataframe("Regional Sales") mine = sales[sales["manager_email"] == viewer]
return mine.to_dict("records")Because the connection already enforces the viewer’s project permissions, this filtering is additional shaping on top of what they’re allowed to see — not a substitute for PlaidCloud’s own access control.
Public Apps
Section titled “Public Apps”If you published with Allow Public Access, the app serves without a sign-in, so there is no viewer to act as — current_user() returns an empty string and get_connection() has no session token to authenticate with, so it raises. Keep public apps to data that’s safe for anyone, or leave public access off so viewers sign in and the per-user model above applies.