Skip to Content
Applications

Applications

Applications are under active development and are not yet stable. Breaking changes may happen between releases without warning. Feedback and bug reports are welcome via GitHub Issues .

Build a app when you need new operations. Build an application when that app becomes the backend for a user-facing workflow with UI, policy, storage, schedules, or other provider bindings.

Gestalt can be used to build applications. At a minimum, an application is a single app, or optionally a composition of apps and UIs, using existing primitives for authentication, authorization, IndexedDB/Cache/S3, workflows, and agents.

Application Shape

This example application, workspace_review, stores review items in IndexedDB, writes generated reports to S3, runs a nightly cron through the workflow provider, and serves a lightweight browser UI at /workspace-review.

Use a app/ plus sibling ui/ layout:

workspace-review/ app/ manifest.yaml package.json provider.ts ui/ manifest.yaml package.json package-lock.json src/ app.tsx

The app manifest owns the operations and points at the sibling UI bundle. For the full manifest schema, see Provider Manifests.

kind: app source: github.com/acme/apps/workspace-review version: 0.1.0 displayName: Workspace Review description: Review workspace changes and publish nightly summaries. spec: mcp: true connections: default: mode: none auth: type: none ui: path: ../ui/manifest.yaml

The UI bundle should be authored from source. Check in the UI source and dependency lockfile, declare a build command and output directory, and leave the generated out/ or dist/ directory out of source control. Gestalt builds the static assets during preparation and serves the prepared output at runtime. Route rules are required when the mounted app binds an authorizationPolicy. For UI package details, see Providers > UI.

Source-built UI packages are the recommended shape for new applications, but the workflow is still alpha. Current manifests need explicit build commands, output paths, and build tools in the preparation environment; improving those ergonomics is on the radar.

kind: ui source: github.com/acme/ui/workspace-review version: 0.1.0 displayName: Workspace Review UI description: Browser UI for workspace review. build: command: - npm - run - build inputs: - package.json - package-lock.json - src spec: assetRoot: out routes: - path: / allowedRoles: [viewer, admin] - path: /admin/* allowedRoles: [admin]

Host Services

The deployment config wires the application into host services. Platform authentication protects the mounted UI and HTTP operation routes, the authorization policy controls who may use the application, and the provider bindings expose only the host services the app is allowed to use.

server: baseUrl: https://gestalt.example.com encryptionKey: ${GESTALT_ENCRYPTION_KEY} providers: authentication: oidc authorization: authz indexeddb: main authorization: policies: workspace_review: default: deny members: - subjectID: service_account:workspace-review-nightly role: admin providers: authentication: oidc: source: package: github.com/valon-technologies/gestalt-providers/auth/oidc version: 0.0.1-alpha.1 config: issuerUrl: https://login.example.com clientId: ${OIDC_CLIENT_ID} clientSecret: secret: provider: default name: oidc-client-secret authorization: authz: source: package: github.com/valon-technologies/gestalt-providers/authorization/indexeddb version: 0.0.1-alpha.1 indexeddb: provider: main db: authz indexeddb: main: source: package: github.com/valon-technologies/gestalt-providers/indexeddb/relationaldb version: 0.0.1-alpha.1 config: dsn: ${DATABASE_URL} s3: reports: source: package: github.com/valon-technologies/gestalt-providers/s3/s3 version: 0.0.1-alpha.1 config: ... workflow: local: source: package: github.com/valon-technologies/gestalt-providers/workflow/indexeddb version: 0.0.1-alpha.1 default: true indexeddb: provider: main db: workflow config: pollInterval: 1s apps: workspace_review: source: ./workspace-review/app/manifest.yaml auth: provider: server authorizationPolicy: workspace_review ui: path: /workspace-review indexeddb: provider: main db: workspace_review objectStores: - items - reports s3: - reports capabilities: workflow: operations: - events.publish workflows: schedules: nightly_summary: provider: local cron: "0 3 * * *" timezone: America/New_York runAs: subject: id: service_account:workspace-review-nightly target: steps: - id: generate_report app: name: workspace_review operation: reports.generateNightly input: bucket: workspace-review-reports

For the full configuration reference, see Config File. For workflow schedules and event triggers, see Providers > Workflow. For the service_account:workspace-review-nightly subject and runAs, see Service Accounts.

Application Logic

In app code, open host services from the request context or language SDK. The app does not know database DSNs, S3 credentials, or workflow storage details. Those come from the deployment bindings.

package provider import ( "context" "encoding/json" "net/http" "time" gestalt "github.com/valon-technologies/gestalt/sdk/go" ) type Provider struct{} type SaveItemInput struct { ID string `json:"id" required:"true"` Title string `json:"title" required:"true"` Status string `json:"status" required:"true"` } type SaveItemOutput struct { OK bool `json:"ok"` } func (p *Provider) saveItem( ctx context.Context, input SaveItemInput, _ gestalt.Request, ) (gestalt.Response[SaveItemOutput], error) { db, err := gestalt.IndexedDB() if err != nil { return gestalt.Response[SaveItemOutput]{}, err } defer db.Close() items := db.ObjectStore("items") if err := items.Put(ctx, gestalt.Record{ "id": input.ID, "title": input.Title, "status": input.Status, "updatedAt": time.Now().UTC().Format(time.RFC3339), }); err != nil { return gestalt.Response[SaveItemOutput]{}, err } return gestalt.OK(SaveItemOutput{OK: true}), nil } type NightlyReportInput struct { Bucket string `json:"bucket" required:"true"` } type NightlyReportOutput struct { ReportURL string `json:"reportUrl"` } func (p *Provider) generateNightly( ctx context.Context, input NightlyReportInput, req gestalt.Request, ) (gestalt.Response[NightlyReportOutput], error) { db, err := gestalt.IndexedDB() if err != nil { return gestalt.Response[NightlyReportOutput]{}, err } defer db.Close() s3, err := gestalt.S3("reports") if err != nil { return gestalt.Response[NightlyReportOutput]{}, err } defer s3.Close() body, err := json.Marshal(map[string]string{ "generatedAt": time.Now().UTC().Format(time.RFC3339), }) if err != nil { return gestalt.Response[NightlyReportOutput]{}, err } obj := s3.Object(input.Bucket, "nightly/"+req.IdempotencyKey+".json") if _, err := obj.WriteBytes(ctx, body, &gestalt.WriteOptions{ ContentType: "application/json", }); err != nil { return gestalt.Response[NightlyReportOutput]{}, err } access, err := obj.CreateAccessUrl(ctx, &gestalt.ObjectAccessURLOptions{ Method: gestalt.PresignMethodGet, Expires: 15 * time.Minute, }) if err != nil { return gestalt.Response[NightlyReportOutput]{}, err } if err := db.ObjectStore("reports").Put(ctx, gestalt.Record{ "id": req.IdempotencyKey, "url": access.URL, "createdAt": time.Now().UTC().Format(time.RFC3339), }); err != nil { return gestalt.Response[NightlyReportOutput]{}, err } workflows, err := req.Workflow() if err != nil { return gestalt.Response[NightlyReportOutput]{}, err } _, err = workflows.PublishEvent(ctx, gestalt.WorkflowPublishEvent{ Event: &gestalt.WorkflowEvent{ Type: "workspace_review.report.generated", Source: "workspace_review", Subject: "nightly", DataContentType: "application/json", Data: map[string]any{"reportUrl": access.URL}, }, }) if err != nil { return gestalt.Response[NightlyReportOutput]{}, err } return gestalt.OK(NightlyReportOutput{ReportURL: access.URL}), nil } var Router = gestalt.MustRouter( gestalt.Register( gestalt.Operation[SaveItemInput, SaveItemOutput]{ ID: "items.save", Method: http.MethodPost, }, (*Provider).saveItem, ), gestalt.Register( gestalt.Operation[NightlyReportInput, NightlyReportOutput]{ ID: "reports.generateNightly", Method: http.MethodPost, }, (*Provider).generateNightly, ), )

Calling Another App From Your Application

Use App when executable app code needs to call another configured app as part of the same request. The caller must declare the allowed call graph in apps.<caller>.invokes; at runtime the SDK sends the current invocation token to the host, and the host preserves the original subject, credential context, and permission ceiling for the nested call.

apps: triage: source: ./apps/triage/manifest.yaml invokes: - app: github operations: - issues.get github: source: ./apps/github/manifest.yaml
import ( "context" "encoding/json" gestalt "github.com/valon-technologies/gestalt/sdk/go" ) func loadLinkedIssue(ctx context.Context, request gestalt.Request, issueNumber int) (map[string]any, error) { app, err := request.App() if err != nil { return nil, err } defer app.Close() result, err := app.Invoke(ctx, "github", "issues.get", map[string]any{ "owner": "acme", "repo": "roadmap", "number": issueNumber, }, &gestalt.InvokeOptions{ Connection: "default", IdempotencyKey: request.IdempotencyKey, }) if err != nil { return nil, err } var issue map[string]any if err := json.Unmarshal([]byte(result.Body), &issue); err != nil { return nil, err } return issue, nil }

The nested call must fit inside the caller’s configured invokes grant. Pass connection, instance, or an idempotency key when the target app should use a specific connected account, provider instance, or retry boundary. For raw GraphQL surfaces, use InvokeGraphQL, invoke_graphql, or invokeGraphQL in the same SDK client.

Lightweight Frontend

The frontend can stay small because Gestalt already handles sessions, route authorization, and static asset serving. A mounted UI calls the app operations on the same origin:

export async function saveItem(item) { const response = await fetch("/api/v1/workspace_review/items.save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(item), }); if (!response.ok) { throw new Error(`Save failed: ${response.status}`); } return response.json(); }

Use Providers > UI for UI bundle configuration and route authorization rules. Source-built UI packages are the recommended shape for new applications; generated static output is prepared by Gestalt and should not normally be checked in.

Configuring Webhooks

Applications can expose product-specific webhooks through the same app that backs the UI, scheduled work, and host-service bindings. Declare the hosted HTTP route in the application app manifest, and implement webhooks.github in the SDK language of choice like any other application operation. The following example configures the public webhook URL as /api/v1/workspace_review/webhooks/github.

kind: app source: github.com/acme/apps/workspace-review version: 0.1.0 displayName: Workspace Review description: Review workspace changes and publish nightly summaries. spec: mcp: true connections: default: mode: none auth: type: none securitySchemes: github_webhook: type: hmac secret: env: GITHUB_WEBHOOK_SECRET signatureHeader: X-Hub-Signature-256 signaturePrefix: sha256= payloadTemplate: "{raw_body}" http: github_webhook: path: /webhooks/github method: POST credentialMode: none security: github_webhook target: webhooks.github requestBody: required: true content: application/json: {} ack: status: 202 body: status: accepted ui: path: ../ui/manifest.yaml

Local Development

Run the app locally from the app directory with the supporting provider bindings in a development config file:

gestaltd provider validate --path ./workspace-review/app --config ./dev.yaml gestaltd serve --path ./workspace-review/app --config ./dev.yaml

For local UI detection, config overlays, and remote attach workflows, see App > Testing locally.