An agent can build a working data app in minutes. Running it safely in production is the hard part.
If you've vibe-coded anything, you know how it goes. You describe the app you want, an agent writes it, and a few minutes later you have a working pipeline tracker or capacity dashboard running on your laptop. The person who knew what the app should do built it, and it works.
Then the questions start:
- Login. How do teammates sign in?
- Permissions. How do you make sure a rep sees different rows than the CFO?
- Hosting. Where does the app run, how do people find it, and what keeps it up when a server fails or the whole team opens it at once?
- Passwords. The app talks to the warehouse, so the app holds a warehouse password, usually pasted into a config file by someone racing a deadline. Who else can read it?
- Versions. When someone renames a field or a chart library changes, what keeps the app from breaking?
Most vibe-coded data apps stop here.
The usual fix is to add all of that to the app: a login page, a proxy to hide the password, and a small backend. Now every data app is its own service, with its own login code, its own rules for who sees what, and someone on the hook to keep it running. The same agent usually writes that code in the same session, and it's the last code you want deciding who can see revenue. Repeat that for every app people build, and you end up with dozens of unreviewed apps with direct access to company data.
A better fix is to make the app simpler. The app should be just files: HTML and JavaScript, with no server, no password, and no login code. Credible handles the rest.
The app is just files
The GraphQL post showed how a dashboard works like a GraphQL app: it sends its queries to one server, and the rules in the data model decide what comes back. Kyle's post made the case that the data model is your data's API. The next step follows from both: once the data model controls access to the data, the app doesn't need a password, login code, or a backend of its own.
On Malloy Publisher, and on Credible, which runs Publisher for you, a data app is a public/ folder inside a Malloy package:
Only the public/ folder is sent to the browser. The data model and the data stay private. Because the app's files, chart library included, live in the same package as the data model, they're published together as one version. The app gets data one way:
<script src="/sdk/publisher.js"></script>
<script type="module" src="./app.js"></script>const rows = await Publisher.query(
"pipeline.malloy",
"run: opportunities -> by_stage",
)Every query goes through the gateway as the person viewing the app. The app never touches the warehouse directly.
The rule for who gets an answer lives next to the data it protects, in pipeline.malloy:
given:
#(secure)
GROUPS :: string[]
#(secure)
TERRITORIES :: string[]
#(authorize) 'sales' in $GROUPS
#(access_filter) territory in $TERRITORIES
source: opportunities is duckdb.table('pipeline.parquet') extend {
measure: opportunity_count is count()
view: by_stage is {
group_by: stage
aggregate: opportunity_count
}
}Credible fills in $GROUPS and $TERRITORIES from the viewer's verified identity, and no query can widen them. Someone outside sales is refused, a rep sees their own territories, and the CFO sees every territory they've been assigned. Fine-grained access control is part of the Enterprise plan, and Access Control covers assigning the values.
What Credible handles for you
Once the app is in a package, Credible handles the five things from the start of this post. The agent didn't have to write any of them.
Login. On Credible, the viewer is already signed in, and the gateway attaches their identity to every query. The app doesn't need Auth0 or token handling of its own.
Permissions. The data model's access rules decide which sources, dimensions, measures, and rows each viewer gets. A rep and the CFO open the same link and see different rows, and no app code made that decision. Security review gets simpler as well: you review the data model once instead of each app's login code, and the data model protects the data for every app, agent, and dashboard. Curate What Your Agents See covers how to write those rules.
Hosting. A package with a public/ folder shows up in the workspace's Data Apps section automatically. There's no server to set up, no link to pass around on Slack, and no "I'll deploy it somewhere this week." The app runs in a sandboxed iframe that resizes to fit its content, so it can sit inside another Credible page without either side having to trust the other.
Credible runs the app in production, too. Each published version is loaded onto multiple workers across availability zones, so losing a server doesn't take the app down, and the services behind it scale out automatically as more people use it. Credible watches query speed and cost, and uses materialization and caching to keep busy charts fast. Enterprise plans add a 99.99% uptime SLA. The architecture doc covers how it all works.
Passwords. The app holds none. Credible securely stores your warehouse credentials and connects to the warehouse itself. The app only talks to Credible, so there's no password in the app's files to leak.
Versions. The app is published with its package, so everything the app needs ships together as one version that can't change after it's published: the HTML, the JavaScript, the chart library in public/vendor/, and the data model the app queries. Viewers get the version you reviewed, even if someone edits the files afterward. A running app keeps using the data model version it was published with, so renaming a field in a later version can't break it. Rolling back the package rolls back the app and the data model together, and a new version starts serving only after it's fully loaded, so publishing and rolling back cause no downtime. Publishing also fits your normal git workflow through CI: a change is a pull request, and publishing happens when it merges.
Credible also connects the app back to the agent. A chart in a data app can pass its exact query and its rows to the Credible agent. So when someone asks "why did this dip in March?", the agent starts with the data behind the chart, and the follow-up uses the same data model as the chart.
Publisher's open-source skills require the agent to check its work before calling the app done: validate every query against the data model, load the finished page and confirm every chart shows real numbers, and unit-test any calculations the app does itself. The skills also teach the agent to use real field names from the data model, show missing data as missing instead of zero, and give every chart its own loading and error states. There's no build step, so the files the agent tested are exactly the files that get served. Dashboards Aren't Dead. WYSIWYG Builders Are. covers why that care matters.
Try it
Start with a data model, because the app is only as governed as the model behind it. Then ask the in-app agent for the app you want, for example "a pipeline tracker with open opportunities by stage, filterable by territory, and a chart of bookings by month". If you'd rather use your own coding agent, point it at the malloy-html-data-apps skill, which has it write a short design brief before any code. Publish the package, and the app shows up in your team's Data Apps section. The data apps doc has the file layout and the full build loop.
Build it in minutes, run it safely
Until now, running a vibe-coded data app safely took far longer than building it, so most of them never left the laptop they were built on.
With Credible, that work is already done. The app is just files in a Malloy package. Credible signs the viewer in, the data model decides what each viewer can see, the app shows up for your team automatically and runs on infrastructure built for production, the warehouse password never leaves Credible, and the app, its libraries, and its data model ship together as one version.
So the person who knows what the app should do can build it, ship it, and hand it to their team the same day.