Deloc is built for static apps, but most dashboards need live numbers. Upload data files directly, or schedule a pipeline that pulls from BigQuery, Snowflake, Postgres, or any HTTP source into your app on a cron.
Data files
Any deployed app can store static data files alongside its HTML and JS. Your app fetches them with regular fetch — same origin, no CORS.
Supported types: .csv, .json, .tsv, .xml, .txt
Max size per file: 10MB
Filenames: alphanumeric plus . _ -; no dotfiles, no reserved names (index.html, manifest.json, etc.)
# Single file
deloc upload-data sales-dash ./data.json
# Multiple files in one call
deloc upload-data sales-dash ./sales.csv ./forecast.csv ./config.json
# Rename on upload (single file only)
deloc upload-data sales-dash ./latest-run-8473.json --filename data.json
Re-uploading a file with the same name replaces it — no versioning. Your app picks up the new data on the next fetch. Cache-bust with a query string if you need immediacy:
ts
fetch("./data.json?t=" + Date.now())
Fetching from your app
ts
// In any page on the app subdomain
const resp = await fetch("./data.json");
const rows = await resp.json();
Same-origin requests, so no CORS headaches, no preflight. Reference from HTML the same way — <img src="./chart.png"> or <script src="./config.js"> all work.
Scheduled refresh — the big picture
For dashboards that need fresh numbers, pair your static app with a scheduled backend that queries your data source and uploads the result. Deloc's MCP server scaffolds the entire backend for you:
shell
# From Claude Code / Cursor / any MCP-enabled editor:
"Set up a BigQuery refresh for sales-dash that runs SELECT * FROM sales.daily"
# Or equivalently via the MCP tool:
# setup_data_refresh({ slug: "sales-dash", source: "bigquery", query: "SELECT ..." })
The tool writes four files into ./data-refresh/:
main.py — queries your source, POSTs JSON to your app
requirements.txt — Python deps
Dockerfile — Alpine Python 3.12, Cloud Run ready
README.md — scheduling instructions
What gets written to your app
The generated script POSTs the query result as JSON to /api/apps/{slug}/data, which stores it as data.json in your app's R2 bucket. Your app fetches it via fetch('./data.json') like any other static file.
Data sources
Pick the tab that matches your source:
main.py (run_query function)
import google.auth
from google.cloud import bigquery
GCP_PROJECT = os.environ.get("GCP_PROJECT", "my-project")
QUERY = """
SELECT region, SUM(revenue) AS total
FROM sales.orders
WHERE order_date >= CURRENT_DATE() - 30
GROUP BY region
"""
def run_query() -> list[dict]:
credentials, _ = google.auth.default(
scopes=["https://www.googleapis.com/auth/bigquery"],
)
client = bigquery.Client(project=GCP_PROJECT, credentials=credentials)
job = client.query(QUERY)
return [dict(row) for row in job.result()]
Env vars required:
GOOGLE_APPLICATION_CREDENTIALS — path to a service account JSON key
GCP_PROJECT — your GCP project ID
In Cloud Run, skip the key file and attach a service account with BigQuery Data Viewer + Job User roles instead.
Scheduling the refresh
Once you have the pipeline, you need something to run it on a schedule. The generated README.md covers three options:
The recommended production setup. Fully managed, logs in Cloud Logging, scales to zero.
shell
# 1. Build and push the image
docker build -t gcr.io/YOUR_PROJECT/deloc-refresh-sales .
docker push gcr.io/YOUR_PROJECT/deloc-refresh-sales
# 2. Create the Cloud Run Job
gcloud run jobs create deloc-refresh-sales \
--image gcr.io/YOUR_PROJECT/deloc-refresh-sales \
--set-env-vars DELOC_TOKEN=dl_xxxxx,DELOC_APP_SLUG=sales-dash,GCP_PROJECT=YOUR_PROJECT
# 3. Schedule it hourly
gcloud scheduler jobs create http deloc-refresh-sales-trigger \
--schedule "0 * * * *" \
--uri "https://YOUR_REGION-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/YOUR_PROJECT/jobs/deloc-refresh-sales:run" \
--http-method POST \
--oauth-service-account-email YOUR_SA@YOUR_PROJECT.iam.gserviceaccount.com
For BigQuery specifically, give your Cloud Run job's service account roles/bigquery.dataViewer and roles/bigquery.jobUser. No service account key file needed — Cloud Run uses attached identity.
Check it ran
gcloud run jobs executions list --job deloc-refresh-sales shows every run. Logs go to Cloud Logging; filter by resource.type="cloud_run_job".
End-to-end: BigQuery to dashboard, every hour
Putting it all together. You have a static dashboard (built with Vite / your choice) that reads data.json. You want it refreshed every hour from a BigQuery query.
1. Deploy the dashboard
shell
cd ./my-dashboard
npm run build
deloc deploy --name "sales-dash"
# → https://your-username.deloc.dev/sales-dash
Your dashboard reads ./data.json — for now it's empty or contains stub data. That's fine.
2. Generate the refresh pipeline
From an MCP-enabled editor, say:
text
"Set up a BigQuery refresh for sales-dash.
The query is: SELECT region, SUM(revenue) AS total
FROM sales.orders WHERE order_date >= CURRENT_DATE() - 30
GROUP BY region"
The setup_data_refresh tool writes main.py, requirements.txt, Dockerfile, and README.md into ./data-refresh/. Alternatively, create a Python project by hand using the source templates above.
3. Sanity-check locally
shell
cd data-refresh
pip install -r requirements.txt
export GCP_PROJECT=your-project
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json
export DELOC_TOKEN=dl_xxxxx
python main.py
# → Fetched 42 rows from bigquery
# → Pushed 42 rows to sales-dash (4,231 bytes)
Hit https://...deloc.dev/sales-dash/data.json in a browser — you should see the JSON your script just uploaded.
4. Deploy to Cloud Run
shell
# Authenticate & set project
gcloud auth login
gcloud config set project YOUR_PROJECT
# Create a service account for the job (once)
gcloud iam service-accounts create deloc-refresh
gcloud projects add-iam-policy-binding YOUR_PROJECT \
--member "serviceAccount:deloc-refresh@YOUR_PROJECT.iam.gserviceaccount.com" \
--role "roles/bigquery.dataViewer"
gcloud projects add-iam-policy-binding YOUR_PROJECT \
--member "serviceAccount:deloc-refresh@YOUR_PROJECT.iam.gserviceaccount.com" \
--role "roles/bigquery.jobUser"
# Build & push the image
docker build -t gcr.io/YOUR_PROJECT/deloc-refresh-sales .
docker push gcr.io/YOUR_PROJECT/deloc-refresh-sales
# Create the Cloud Run Job
gcloud run jobs create deloc-refresh-sales \
--image gcr.io/YOUR_PROJECT/deloc-refresh-sales \
--service-account deloc-refresh@YOUR_PROJECT.iam.gserviceaccount.com \
--set-env-vars DELOC_TOKEN=dl_xxxxx,DELOC_APP_SLUG=sales-dash,GCP_PROJECT=YOUR_PROJECT
Use Secret Manager in production
For any real deployment, store DELOC_TOKEN in Google Secret Manager and mount it via --set-secrets DELOC_TOKEN=deloc-token:latest instead of --set-env-vars. Env vars are visible in the Cloud Console to anyone with run.jobs.get.
# Trigger the scheduler once, right now
gcloud scheduler jobs run deloc-refresh-sales-trigger --location YOUR_REGION
# Watch the execution
gcloud run jobs executions list --job deloc-refresh-sales
# Inspect app data
curl https://your-username.deloc.dev/sales-dash/data.json | jq .
That's the whole loop. New data every hour. Zero dashboard re-deploys.
macOS launchd (for dev machines)
If you want to run the refresh on your laptop while developing, launchd is cleaner than cron on macOS. setup_data_refresh doesn't emit a plist, but here's a working template:
Output goes to /tmp/deloc-refresh.log and /tmp/deloc-refresh.err. tail -f them after loading the agent to confirm the first run at the top of the hour.