Applications
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.tsxThe 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.yamlThe 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-reportsFor 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.
Go
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.yamlGo
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.yamlLocal 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.yamlFor local UI detection, config overlays, and remote attach workflows, see App > Testing locally.