{#if loading && !app}

Loading…

{:else if loadError && !app}
Could not load app.

{loadError}

Back to apps
{:else if app} {#if activeTab === 'scripts'}

Scripts

{#if canWrite} {/if}
{#if showCreateScript && canWrite}
{#if createScriptKind === 'module'}

Modules expose fn and const declarations to other scripts via import "name" as alias;. They cannot be bound to routes or used as trigger targets.

{/if} {#if createScriptError}
{createScriptError}
{/if}
{/if} {#if scripts.length === 0}

No scripts in this app yet.

{:else} {/if}
{:else if activeTab === 'domains'}

Domain claims

Hosts this app answers on. Routes inside this app can only bind to these. Use app.example.com for exact, *.example.com for wildcard, or {'{'}tenant{'}'}.example.com to bind a capture.

{#if canAdmin}
{#if createDomainError}
{createDomainError}
{/if} {/if} {#if domains.length === 0}

No domain claims yet.

{:else} {/if}
{:else if activeTab === 'members' && canAdmin}

Members

Users with explicit access to this app. Instance owners and admins already have implicit access — they are not listed here. Use the Users page to invite a member first, then grant them app access below.

{#if eligibleLoadError}

{eligibleLoadError}

{:else if eligibleAfterFilter.length === 0}

No eligible users to invite. Create a member on the Users page first.

{/if} {#if addMemberError}
{addMemberError}
{/if}
{#if memberActionError}
{memberActionError}
{/if} {#if members.length === 0}

No explicit members yet.

{:else}
User
Instance
App role
Joined
{#each members as m (m.user_id)}
{m.username} {#if m.email}{m.email}{/if} {#if !m.is_active}(inactive){/if}
{shortDate(m.created_at)}
changeMemberRole(m, 'app_admin') }, { label: 'Make editor', disabled: m.role === 'editor' || roleChangeBusy === m.user_id, onClick: () => changeMemberRole(m, 'editor') }, { label: 'Make viewer', disabled: m.role === 'viewer' || roleChangeBusy === m.user_id, onClick: () => changeMemberRole(m, 'viewer') }, { label: 'Remove from app', danger: true, onClick: () => askRemoveMember(m) } ]} />
{/each}
{/if}
{:else if activeTab === 'triggers' && canAdmin}

Cron triggers

Run an endpoint script on a schedule. Schedules are 6-field cron expressions (with seconds): sec min hour day-of-month month day-of-week. The timezone disambiguates schedules like "every weekday at 9am".

{#if endpointScripts.length === 0}

This app has no endpoint scripts yet — create one first (modules can't be trigger targets).

{/if} {#if createCronError}
{createCronError}
{/if}

Pub/Sub triggers

Subscribe an endpoint script to durable pub/sub messages. Topic patterns are an exact topic (user.created), a prefix wildcard (user.*), or * for every topic.

{#if createPubsubError}
{createPubsubError}
{/if}

Email triggers

Fire an endpoint script when your email provider POSTs an inbound message to PiCloud. Configure your provider (Mailgun / Postmark / SendGrid / SES) to POST the generic JSON shape below to the trigger's webhook URL. Set a shared secret to require an X-Picloud-Signature HMAC-SHA256 (hex of the request body); leave it blank to accept unsigned POSTs (URL secrecy only).

Expected inbound JSON shape
{`{
  "from": "sender@external.com",
  "to": ["alice@myapp.com"],
  "cc": [],
  "subject": "...",
  "text": "...",
  "html": "...",
  "message_id": ""
}`}
{#if createEmailError}
{createEmailError}
{/if}

Queue:receive trigger (v1.1.9)

Register a script as the consumer for a per-app durable queue. Exactly one consumer per queue is allowed — the registration will be rejected if another consumer already exists. Use the Queues tab for a read-only view of message depths.

{#if createQueueError}
{createQueueError}
{/if}

Create kv:event trigger

Ops:
{#if createKvError}
{createKvError}
{/if}

Create docs:event trigger

Ops:
{#if createDocsError}
{createDocsError}
{/if}

Create files:event trigger

Ops:
{#if createFilesError}
{createFilesError}
{/if}

Create dead_letter trigger

Fires when any other trigger or route dead-letters. All filter fields are optional; leaving them blank routes every dead-letter to this script.

{#if createDeadLetterError}
{createDeadLetterError}
{/if}
{#if triggers.length === 0}

No triggers in this app yet.

{:else} {/if}
{:else if activeTab === 'topics' && canAdmin}

Realtime topics

Pub/sub topics are internal-only by default — scripts subscribe via triggers, browsers can't. Register a topic here and mark it externally subscribable to let frontend clients connect over SSE at /realtime/topics/<name>. public topics need no auth; token topics require a subscriber token minted by a script via pubsub::subscriber_token.

{#if createTopicExternal}
Auth mode
{/if} {#if createTopicError}
{createTopicError}
{/if}
{#if topics.length === 0}

No registered topics in this app yet.

{:else} {/if}
{:else if activeTab === 'secrets' && canAdmin}

Secrets

Encrypted per-app configuration (API keys, OAuth tokens, webhook signing keys), available to scripts as secrets::get("name"). Values are encrypted at rest with the process master key and never leave the server — this list shows names and last-modified times only.

{#if secretNameExists && createSecretName.trim()}

A secret named {createSecretName.trim()} already exists — saving overwrites it.

{/if} {#if createSecretError}
{createSecretError}
{/if}
{#if secrets.length === 0}

No secrets in this app yet.

{:else} {/if}
{:else if activeTab === 'settings' && canAdmin}

Settings

saveSettings(e)}> {#if slugTakeoverNeeded}
Slug previously redirected.

{editSlug} currently redirects to {slugTakeoverNeeded.slug}. Renaming to it will break old links.

{:else if settingsError}
{settingsError}
{/if} {#if !slugTakeoverNeeded}
{/if}

Delete app

Permanently removes the app along with all its scripts, routes, execution logs, and domain claims.

{/if} {#if confirmingDeleteApp} (confirmingDeleteApp = false)} >

This will permanently delete everything inside {app.name}. There is no undo.

{#if domains.length > 0}

The following hosts will stop pointing at this app:

{/if} {#if deleteAppError} {/if}
{/if} {#if domainToRemove} (domainToRemove = null)} >

{app.name} will stop answering on {domainToRemove.pattern}.

Routes already bound to this host are blocked from deletion by the API; if so, you’ll see an error here.

{#if removeDomainError} {/if}
{/if} {#if memberToRemove} (memberToRemove = null)} >

{memberToRemove.username} will lose access to this app. Their other app memberships and account are untouched.

{#if removeMemberError} {/if}
{/if} {#if triggerToRemove} (triggerToRemove = null)} >

This {triggerToRemove.kind} trigger will stop firing. The target script is not affected.

{/if} {#if topicToEdit} (topicToEdit = null)} > {#if editTopicExternal}
Auth mode
{/if} {#if editFlipToExternal}
Marking {topicToEdit.name} externally-subscribable means anyone with the URL can subscribe to this topic (if auth_mode is public) or anyone with a valid token can subscribe (if auth_mode is token). Are you sure?
{/if} {#if editTopicError} {/if}
{/if} {#if topicToRemove} (topicToRemove = null)} >

Unregistering {topicToRemove.name} disconnects any live SSE subscribers immediately. Scripts can still publish_durable to it (internal triggers keep working) — it just won't be externally subscribable.

{/if} {#if revealConfirm} (revealConfirm = false)} >

The value you type will be shown in plain text on screen. Make sure no one is looking over your shoulder and that screen-sharing is off.

{/if} {#if secretToRemove} (secretToRemove = null)} >

Deleting {secretToRemove.name} is permanent. Any script calling secrets::get("{secretToRemove.name}") will get () until you set it again.

{/if} {/if}