Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 44 additions & 9 deletions .github/workflows/docs-build.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
name: Docs

# Builds the documentation site in docs/. The API reference is generated from
# openapi.yaml, so a spec change rebuilds the docs too — this is a second
# consumer of spec correctness alongside code-quality.yml's Spectral lint.
# Builds the documentation site in docs/ and publishes the checked artifact to
# GitHub Pages from main. The API reference is generated from openapi.yaml, so
# a spec change rebuilds the docs too.
#
# The Go workflows (tests.yml, code-quality.yml) are path-filtered to Go and
# spec files, so a docs-only change does not run them.

on:
pull_request:
paths:
- 'docs/**'
- 'openapi.yaml'
- '.github/workflows/docs-build.yml'
- "docs/**"
- "openapi.yaml"
- ".github/workflows/docs-build.yml"
push:
branches: [main]
paths:
- "docs/**"
- "openapi.yaml"
- ".github/workflows/docs-build.yml"
workflow_dispatch:

concurrency:
group: docs-${{ github.head_ref }}
cancel-in-progress: true
group: docs-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'pages' }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read
Expand Down Expand Up @@ -50,7 +57,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '24'
node-version: "24"
cache: pnpm
cache-dependency-path: docs/pnpm-lock.yaml

Expand All @@ -72,3 +79,31 @@ jobs:
# hand-checks missed twice.
- name: Check internal links
run: pnpm check:links

- name: Check legacy redirects
run: pnpm check:redirects

# Only a main-branch run can publish. PR builds have read-only permissions
# and still exercise the exact same checks before merge.
- name: Upload Pages artifact
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
with:
path: docs/dist

deploy:
name: Deploy docs to GitHub Pages
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy Pages artifact
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
50 changes: 42 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ no external service in the build: `pnpm build` works offline with no API keys.

Run these from this directory (`docs/`):

| Command | What it does |
| -------------- | ---------------------------------------------------- |
| `pnpm install` | Install dependencies |
| `pnpm dev` | Dev server at http://localhost:4321 |
| `pnpm build` | Production build into `dist/` |
| `pnpm preview` | Serve the built site locally |
| `pnpm check` | `astro check` — types and content-collection schemas |
| `pnpm format` | Prettier across this directory |
| Command | What it does |
| ---------------------- | ---------------------------------------------------- |
| `pnpm install` | Install dependencies |
| `pnpm dev` | Dev server at http://localhost:4321 |
| `pnpm build` | Production build into `dist/` |
| `pnpm preview` | Serve the built site locally |
| `pnpm check` | `astro check` — types and content-collection schemas |
| `pnpm check:links` | Check built internal links and assets |
| `pnpm check:redirects` | Check built legacy API redirects |
| `pnpm format` | Prettier across this directory |

## Layout

Expand All @@ -37,3 +39,35 @@ Starlight's [components](https://starlight.astro.build/components/using-componen
`@astrojs/starlight/components`.

The API reference is not hand-written. To change it, change `../openapi.yaml`.

## GitHub Pages deployment

`.github/workflows/docs-build.yml` checks PRs and publishes the same checked `docs/dist/`
artifact on changes to `docs/**` or `openapi.yaml` on `main`. It can also be run manually
from `main`. The repository's Pages source must be **GitHub Actions**. The `github-pages`
environment is used by the deploy job; no build secrets are required.

The site uses root-relative URLs and has `https://hub.formbricks.com` as its canonical
origin. For pre-cutover browser checks, set the Pages custom domain to
`hub-docs-preview.formbricks.com` and point an unproxied CNAME there to
`formbricks.github.io`. Check HTTPS, the homepage, guides, API pages, search, assets,
`/sitemap-index.xml`, and every path in `legacy-stainless-routes.json` before
switching the live domain. The default `formbricks.github.io/hub/` project URL cannot
exercise root-relative links accurately.

For cutover, set the Pages custom domain to `hub.formbricks.com` and update that
hostname's unproxied CNAME from `customers.stldocs.app` to `formbricks.github.io` in
the infra repository. Confirm GitHub Pages reports the domain and HTTPS as healthy,
then check the same routes at the live hostname. Keep the old Stainless DNS target and
verification TXT records available through the rollback window. To roll back, restore
the former CNAME target in infra and verify the old site serves again.

`legacy-stainless-routes.json` records the API URLs served by Stainless at migration.
Every entry must have a destination in `legacy-redirects.json`; the build check
enforces this. GitHub Pages serves these old API paths as HTML refresh pages. They
retain browser access and link destinations, but do not return HTTP 301. A true 301
would require an additional redirect service in front of Pages.

Stainless also serves `/index.md` and `/quickstart/index.md` as raw Markdown.
Recreating Markdown exports across the new site is tracked in ENG-2530. The old
`/api/index.md` currently returns 404.
10 changes: 8 additions & 2 deletions docs/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import starlight from "@astrojs/starlight";
import starlightOpenAPI, { openAPISidebarGroups } from "starlight-openapi";
import starlightSidebarTopics from "starlight-sidebar-topics";
import { posthog } from "./src/integrations/posthog";
import legacyRedirects from "./legacy-redirects.json";

// https://astro.build/config
export default defineConfig({
// Canonical origin. Required by @astrojs/sitemap, which Starlight enables —
// without it the sitemap is silently skipped. Revisit with ENG-2370 (hosting).
// Canonical origin. Required by @astrojs/sitemap, which Starlight enables.
site: "https://hub.formbricks.com",
// GitHub Pages serves these as static HTML refresh pages, not HTTP 301s.
redirects: legacyRedirects,
env: {
schema: {
PUBLIC_POSTHOG_KEY: envField.string({
Expand Down Expand Up @@ -166,6 +168,10 @@ export default defineConfig({
slug: "reference/environment-variables",
},
{ label: "Metrics", slug: "reference/metrics" },
{
label: "TypeScript SDK",
slug: "reference/typescript-sdk",
},
],
},
],
Expand Down
46 changes: 46 additions & 0 deletions docs/legacy-redirects.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"/api/resources/enrichment_status": "/api/operations/tags/enrichment-status/",
"/api/resources/enrichment_status/methods/retrieve": "/api/operations/get-enrichment-status/",
"/api/resources/feedback_records": "/api/operations/tags/feedback-records/",
"/api/resources/feedback_records/methods/bulk_delete": "/api/operations/delete-feedback-records-by-user/",
"/api/resources/feedback_records/methods/count": "/api/operations/count-feedback-records/",
"/api/resources/feedback_records/methods/create": "/api/operations/create-feedback-record/",
"/api/resources/feedback_records/methods/delete": "/api/operations/delete-feedback-record/",
"/api/resources/feedback_records/methods/list": "/api/operations/list-feedback-records/",
"/api/resources/feedback_records/methods/retrieve": "/api/operations/get-feedback-record/",
"/api/resources/feedback_records/methods/retrieve_similar": "/api/operations/similar-feedback-records/",
"/api/resources/feedback_records/methods/update": "/api/operations/update-feedback-record/",
"/api/resources/feedback_records/subresources/search": "/api/operations/tags/feedback-records/",
"/api/resources/feedback_records/subresources/search/methods/perform_semantic_search": "/api/operations/semantic-search-feedback-records/",
"/api/resources/health": "/api/operations/tags/health/",
"/api/resources/health/methods/check": "/api/operations/health-check/",
"/api/resources/taxonomy": "/api/operations/tags/taxonomy/",
"/api/resources/taxonomy/methods/list_fields": "/api/operations/list-taxonomy-fields/",
"/api/resources/taxonomy/subresources/nodes": "/api/operations/tags/taxonomy/",
"/api/resources/taxonomy/subresources/nodes/methods/list_records": "/api/operations/list-taxonomy-node-records/",
"/api/resources/taxonomy/subresources/nodes/methods/rename": "/api/operations/rename-taxonomy-node/",
"/api/resources/taxonomy/subresources/nodes/methods/soft_remove": "/api/operations/remove-taxonomy-node/",
"/api/resources/taxonomy/subresources/runs": "/api/operations/tags/taxonomy/",
"/api/resources/taxonomy/subresources/runs/methods/get_tree": "/api/operations/get-taxonomy-run-tree/",
"/api/resources/taxonomy/subresources/runs/methods/list": "/api/operations/list-taxonomy-runs/",
"/api/resources/taxonomy/subresources/runs/methods/retrieve": "/api/operations/get-taxonomy-run/",
"/api/resources/taxonomy/subresources/runs/methods/retrieve_record_counts": "/api/operations/count-taxonomy-node-records/",
"/api/resources/taxonomy/subresources/runs/methods/start": "/api/operations/create-taxonomy-run/",
"/api/resources/taxonomy/subresources/runs/subresources/active": "/api/operations/tags/taxonomy/",
"/api/resources/taxonomy/subresources/runs/subresources/active/methods/get_tree": "/api/operations/get-active-taxonomy-tree/",
"/api/resources/tenants": "/api/operations/tags/tenant-data/",
"/api/resources/tenants/methods/delete_data": "/api/operations/delete-tenant-data/",
"/api/resources/tenants/methods/purge_feedback_records": "/api/operations/purge-tenant-feedback-records/",
"/api/resources/tenants/subresources/enrichments": "/api/operations/tags/enrichment-status/",
"/api/resources/tenants/subresources/enrichments/methods/retry": "/api/operations/retry-tenant-enrichments/",
"/api/resources/tenants/subresources/settings": "/api/operations/tags/tenant-settings/",
"/api/resources/tenants/subresources/settings/methods/retrieve": "/api/operations/get-tenant-settings/",
"/api/resources/tenants/subresources/settings/methods/update": "/api/operations/patch-tenant-settings/",
"/api/resources/webhooks": "/api/operations/tags/webhooks/",
"/api/resources/webhooks/methods/create": "/api/operations/create-webhook/",
"/api/resources/webhooks/methods/delete": "/api/operations/delete-webhook/",
"/api/resources/webhooks/methods/list": "/api/operations/list-webhooks/",
"/api/resources/webhooks/methods/retrieve": "/api/operations/get-webhook/",
"/api/resources/webhooks/methods/update": "/api/operations/update-webhook/",
"/api/typescript": "/reference/typescript-sdk/"
}
46 changes: 46 additions & 0 deletions docs/legacy-stainless-routes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[
"/api/resources/enrichment_status",
"/api/resources/enrichment_status/methods/retrieve",
"/api/resources/feedback_records",
"/api/resources/feedback_records/methods/bulk_delete",
"/api/resources/feedback_records/methods/count",
"/api/resources/feedback_records/methods/create",
"/api/resources/feedback_records/methods/delete",
"/api/resources/feedback_records/methods/list",
"/api/resources/feedback_records/methods/retrieve",
"/api/resources/feedback_records/methods/retrieve_similar",
"/api/resources/feedback_records/methods/update",
"/api/resources/feedback_records/subresources/search",
"/api/resources/feedback_records/subresources/search/methods/perform_semantic_search",
"/api/resources/health",
"/api/resources/health/methods/check",
"/api/resources/taxonomy",
"/api/resources/taxonomy/methods/list_fields",
"/api/resources/taxonomy/subresources/nodes",
"/api/resources/taxonomy/subresources/nodes/methods/list_records",
"/api/resources/taxonomy/subresources/nodes/methods/rename",
"/api/resources/taxonomy/subresources/nodes/methods/soft_remove",
"/api/resources/taxonomy/subresources/runs",
"/api/resources/taxonomy/subresources/runs/methods/get_tree",
"/api/resources/taxonomy/subresources/runs/methods/list",
"/api/resources/taxonomy/subresources/runs/methods/retrieve",
"/api/resources/taxonomy/subresources/runs/methods/retrieve_record_counts",
"/api/resources/taxonomy/subresources/runs/methods/start",
"/api/resources/taxonomy/subresources/runs/subresources/active",
"/api/resources/taxonomy/subresources/runs/subresources/active/methods/get_tree",
"/api/resources/tenants",
"/api/resources/tenants/methods/delete_data",
"/api/resources/tenants/methods/purge_feedback_records",
"/api/resources/tenants/subresources/enrichments",
"/api/resources/tenants/subresources/enrichments/methods/retry",
"/api/resources/tenants/subresources/settings",
"/api/resources/tenants/subresources/settings/methods/retrieve",
"/api/resources/tenants/subresources/settings/methods/update",
"/api/resources/webhooks",
"/api/resources/webhooks/methods/create",
"/api/resources/webhooks/methods/delete",
"/api/resources/webhooks/methods/list",
"/api/resources/webhooks/methods/retrieve",
"/api/resources/webhooks/methods/update",
"/api/typescript"
]
1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"start": "astro dev",
"build": "astro build",
"check:links": "node scripts/check-links.mjs",
"check:redirects": "node scripts/check-legacy-redirects.mjs",
"preview": "astro preview",
"check": "astro check",
"astro": "astro",
Expand Down
Empty file added docs/public/.nojekyll
Empty file.
56 changes: 56 additions & 0 deletions docs/scripts/check-legacy-redirects.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env node
// Verify that the captured Stainless URL inventory has built redirects and real targets.
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";

const dist = process.argv[2] ?? "dist";
const inventory = JSON.parse(
readFileSync("legacy-stainless-routes.json", "utf8"),
);
const redirects = JSON.parse(readFileSync("legacy-redirects.json", "utf8"));
const entries = Object.entries(redirects);
const errors = [];

if (new Set(inventory).size !== inventory.length) {
errors.push("The legacy route inventory contains duplicate paths.");
}
for (const source of inventory) {
if (!(source in redirects)) errors.push(`Missing redirect for ${source}`);
}
for (const [source] of entries) {
if (!inventory.includes(source))
errors.push(`Uninventoried redirect: ${source}`);
}

for (const [source, destination] of entries) {
if (
!/^\/api\/(?:resources\/[a-z0-9_/-]+|typescript)$/.test(source) ||
!/^\/[a-z0-9_/-]+\/$/.test(destination)
) {
errors.push(`Invalid redirect path: ${source} -> ${destination}`);
continue;
}

const sourceFile = join(dist, source.slice(1), "index.html");
const destinationFile = join(dist, destination.slice(1), "index.html");
if (!existsSync(sourceFile)) {
errors.push(`Missing redirect page: ${sourceFile}`);
continue;
}
if (!existsSync(destinationFile)) {
errors.push(`Missing destination page: ${destinationFile}`);
}
const html = readFileSync(sourceFile, "utf8");
if (!html.includes(`http-equiv="refresh" content="0;url=${destination}"`)) {
errors.push(`Wrong redirect destination in ${sourceFile}`);
}
}

if (errors.length) {
for (const error of errors) console.error(error);
process.exit(1);
}

console.log(
`Verified ${entries.length} legacy redirects and their destinations.`,
);
31 changes: 31 additions & 0 deletions docs/src/content/docs/reference/typescript-sdk.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: TypeScript SDK
description: Install and use the official Formbricks Hub TypeScript client.
---

Install the [`@formbricks/hub`](https://www.npmjs.com/package/@formbricks/hub) client with npm:

```bash
npm install @formbricks/hub
```

The client supports Node.js 22 or later and browser runtimes. Set `baseUrl` to the
origin of your Hub deployment and pass an API key:

```ts
import { createHubClient, listFeedbackRecords } from "@formbricks/hub";

const client = createHubClient({
apiKey: process.env.HUB_API_KEY!,
baseUrl: "https://your-hub.example.com",
});

const { data, error } = await listFeedbackRecords({
client,
query: { tenant_id: "your-tenant-id", limit: 50 },
});
```

See the [SDK README](https://github.com/formbricks/hub/tree/main/sdks/typescript)
for retries, timeouts, and response handling. The [API reference](/api/) describes
the available operations.