Skip to content

Commit 76f03d5

Browse files
authored
Run PowerShell Data Loaders on Azure Static Web Apps (#286)
## Summary - register PowerShell as an Observable Framework data-loader interpreter - turn PowerShell output into a static CSV snapshot consumed by an Observable page - build the site in GitHub Actions where `pwsh` is available - deploy the prebuilt `dist` directory to Azure Static Web Apps without an automatic rebuild - explain how to keep diagnostics out of loader stdout ## Validation - `git diff --check upstream/main...HEAD` - `npm run build` - `npm run validate:articles` - executed the article's PowerShell loader against the referenced Vega movies dataset - built the source Observable project and confirmed that its `.ps1` loader generated and copied the CSV snapshot - verified the documented stdout/stderr behavior of `pwsh` - checked the current Observable Framework and Azure Static Web Apps documentation for custom interpreters and prebuilt deployments
1 parent 0b2c672 commit 76f03d5

1 file changed

Lines changed: 202 additions & 0 deletions

File tree

  • content/articles/2026/09/run-powershell-data-loaders-on-azure-static-web-apps
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
---
2+
title: "Run PowerShell Data Loaders on Azure Static Web Apps"
3+
description: "Use PowerShell data loaders in Observable Framework and deploy the generated static site to Azure Static Web Apps without relying on the platform's automatic build environment."
4+
author: Andrey Vernigora
5+
authors:
6+
- Andrey Vernigora
7+
date: "2026-09-28T00:00:00+00:00"
8+
categories:
9+
- Tools
10+
tags:
11+
- powershell
12+
- observable-framework
13+
- azure-static-web-apps
14+
- github-actions
15+
- data-visualization
16+
---
17+
18+
Observable Framework can run PowerShell scripts during a build and expose their
19+
output to interactive pages as static data. The part that needs extra attention is
20+
deployment: the automatic Azure Static Web Apps build does not know about your custom
21+
PowerShell interpreter.
22+
23+
This article shows how to register `.ps1` data loaders, build the site in GitHub
24+
Actions where PowerShell is available, and ask Azure Static Web Apps to deploy the
25+
already-generated files.
26+
27+
## How Observable data loaders work
28+
29+
[Observable Framework](https://observablehq.com/framework/) is a static site
30+
generator for data apps, dashboards, and reports. A page can load a CSV file like
31+
this:
32+
33+
```js
34+
const movies = FileAttachment("movies.csv").csv({typed: true});
35+
```
36+
37+
If `movies.csv` does not exist, Framework looks for a data loader with a double
38+
extension, such as `movies.csv.py` or `movies.csv.js`. It runs the loader during the
39+
build, saves its standard output as a static snapshot, and makes that snapshot
40+
available to the page as `movies.csv`.
41+
42+
Framework supports several interpreters by default and lets us register additional
43+
ones. Add PowerShell to `observablehq.config.js`:
44+
45+
```js
46+
export default {
47+
root: "src",
48+
interpreters: {
49+
".ps1": ["pwsh"]
50+
}
51+
};
52+
```
53+
54+
The `pwsh` executable must be installed and available on `PATH` wherever the site is
55+
built.
56+
57+
## Create a PowerShell data loader
58+
59+
Create `src/movies.csv.ps1`:
60+
61+
```powershell
62+
$uri = 'https://raw.githubusercontent.com/vega/vega/main/docs/data/movies.json'
63+
$movies = Invoke-RestMethod -Uri $uri
64+
65+
$movies |
66+
Select-Object -Property Title, 'Worldwide Gross', 'US Gross', 'IMDB Rating' |
67+
ConvertTo-Csv -NoTypeInformation
68+
```
69+
70+
The script retrieves JSON, selects the columns needed by the page, and writes CSV to
71+
standard output. That last detail is important: standard output becomes the generated
72+
file. When `pwsh` runs as an external process, PowerShell's information, warning,
73+
verbose, and debug streams are also written to standard output, so using them can
74+
corrupt the CSV. Write non-fatal diagnostics directly to standard error, for example
75+
with `[Console]::Error.WriteLine('message')`, and use `throw` when the loader should
76+
fail.
77+
78+
You can test the loader independently:
79+
80+
```powershell
81+
pwsh ./src/movies.csv.ps1
82+
```
83+
84+
Then use it from `src/index.md`:
85+
86+
````markdown
87+
```js
88+
const movies = FileAttachment("movies.csv").csv({typed: true});
89+
```
90+
91+
```js
92+
Inputs.table(movies)
93+
```
94+
````
95+
96+
Run the normal Framework build:
97+
98+
```powershell
99+
npm run build
100+
```
101+
102+
Framework executes the PowerShell loader, caches its result, and includes the
103+
generated CSV in the static output.
104+
105+
## Why the default Azure build can fail
106+
107+
Azure Static Web Apps normally checks out the source and invokes its automatic build
108+
environment. That works until the application requires an interpreter or native tool
109+
that the environment does not provide or configure.
110+
111+
Instead of teaching the automatic builder about every dependency, build the site in
112+
an ordinary GitHub Actions step and deploy only the output directory. This also makes
113+
the failing stage obvious: if a PowerShell loader breaks, the `npm run build` step
114+
fails before deployment starts.
115+
116+
## Keep the Azure configuration in the output
117+
118+
Azure looks for `staticwebapp.config.json` in the deployed directory. If you keep that
119+
file at the project root, copy it after the Observable build.
120+
121+
For example, these scripts in `package.json` build the site into `dist` and copy the
122+
configuration:
123+
124+
```json
125+
{
126+
"scripts": {
127+
"build": "rimraf dist && observable build",
128+
"postbuild": "cp staticwebapp.config.json dist/staticwebapp.config.json"
129+
}
130+
}
131+
```
132+
133+
The `postbuild` script runs automatically after `npm run build`. For a
134+
cross-platform project, replace `cp` with a small Node.js script.
135+
136+
## Build first, then deploy
137+
138+
The relevant part of the GitHub Actions workflow looks like this:
139+
140+
```yaml
141+
jobs:
142+
build_and_deploy:
143+
runs-on: ubuntu-latest
144+
steps:
145+
- name: Check out the repository
146+
uses: actions/checkout@v4
147+
148+
- name: Set up Node.js
149+
uses: actions/setup-node@v4
150+
with:
151+
node-version: 20
152+
cache: npm
153+
154+
- name: Verify PowerShell
155+
shell: pwsh
156+
run: $PSVersionTable.PSVersion
157+
158+
- name: Install dependencies
159+
run: npm ci
160+
161+
- name: Build the Observable site
162+
run: npm run build
163+
164+
- name: Deploy the prebuilt site
165+
uses: Azure/static-web-apps-deploy@v1
166+
with:
167+
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
168+
repo_token: ${{ secrets.GITHUB_TOKEN }}
169+
action: upload
170+
app_location: dist
171+
api_location: ""
172+
output_location: ""
173+
skip_app_build: true
174+
```
175+
176+
The last four settings are the key:
177+
178+
- `app_location` points directly to the generated `dist` directory;
179+
- `output_location` is empty because no build happens inside the Azure action;
180+
- `skip_app_build` prevents the Azure action from invoking its automatic builder;
181+
- `staticwebapp.config.json` is already inside `dist`.
182+
183+
Keep the pull-request close job and deployment token generated for your Static Web
184+
App; only replace the build-and-upload portion of the workflow.
185+
186+
## The resulting build pipeline
187+
188+
The finished pipeline has a simple division of responsibility:
189+
190+
1. GitHub Actions provides Node.js and PowerShell.
191+
2. Observable Framework runs `.ps1` data loaders and produces static files.
192+
3. Azure Static Web Apps deploys those files without rebuilding them.
193+
194+
This pattern is not limited to PowerShell. It also works when an Observable project
195+
depends on another interpreter, command-line tool, or native library that is easier to
196+
control in a dedicated build step.
197+
198+
For more detail, see the Observable Framework documentation for
199+
[data loaders](https://observablehq.com/framework/data-loaders) and
200+
[custom interpreters](https://observablehq.com/framework/config#interpreters), plus
201+
the Azure documentation for
202+
[deploying a prebuilt application](https://learn.microsoft.com/azure/static-web-apps/build-configuration#skip-building-front-end-app).

0 commit comments

Comments
 (0)