Variables
Forge Variables are key-value pairs that store environment-specific configuration for your app — such as external URLs, feature flags, or display settings. Variables are managed separately from the app manifest and can be updated without redeploying.
Overview
Each app has two independent variable sets: one for dev and one for live. This allows you to use different endpoints or settings per environment without changing your code.
Variables are stored as plain text
Do not store secrets, API keys, passwords, or other sensitive values in variables. Variable values are stored unencrypted in the database and are readable by anyone with access to the app's configuration.
Variables are:
- Not part of the manifest — they are managed through the API or SDK, not in
ptkl.config.js - Per-environment — dev and live have completely separate values
- Injected at runtime — available as a global inside services and lifecycle scripts
Managing Variables
Your own variables
An app reads its own variables with no argument and no permission — every app can always do this. The platform resolves which app is asking from the app's own token, so there is nothing to name and nothing to grant.
Requires @ptkl/sdk/beta
The no-argument forms are v0.10 and up. The @ptkl/sdk entry point resolves to v0.9, which
only has the forms that take an app ref.
import { Platform } from '@ptkl/sdk/beta'
const platform = new Platform()
// Read your own variables for the current environment — no permission needed
const vars = await platform.forge().getVariables()
// Replace your own variables — requires `manage_variables forge`
await platform.forge().updateVariables({
WEBHOOK_URL: 'https://example.com/hook',
MAX_RETRIES: 3,
FEATURE_V2_ENABLED: true,
})
// Add or update a single one of your own variables
await platform.forge().addVariable('WEBHOOK_URL', 'https://example.com/hook')
To write its own variables an app declares manage_variables forge in its manifest — as an
entitlement if it should always be able to manage its own configuration regardless of who
launched it:
That permission is self-scoped by construction: it is only honoured on the pathless route, which cannot name another app. Granting it never exposes any other app's variables.
Another app's variables
Pass an app ref to read or write a different app. This requires manage forge, which is a
project-wide administrative permission — it grants read and write over every app's variables,
so do not declare it just to reach your own.
const vars = await platform.forge().getVariables('my-app')
await platform.forge().updateVariables('my-app', { WEBHOOK_URL: '...' })
await platform.forge().addVariable('my-app', 'WEBHOOK_URL', '...')
Permissions at a glance
| Call | Permission required |
|---|---|
| Read your own variables | none |
| Write your own variables | manage_variables forge |
| Read or write another app's variables | manage forge |
Using the API
# Read your own variables (dev environment) — no permission required
curl https://api.ptkl.app/v1/forge/variables \
-H "X-Project-Env: dev" \
-H "Authorization: Bearer <app-token>"
# Update your own variables — requires `manage_variables forge`
curl -X PATCH https://api.ptkl.app/v1/forge/variables \
-H "X-Project-Env: dev" \
-H "Authorization: Bearer <app-token>" \
-H "Content-Type: application/json" \
-d '{"WEBHOOK_URL": "https://staging.example.com/hook", "MAX_RETRIES": 3}'
# Read another app's variables — requires `manage forge`
curl https://api.ptkl.app/v1/forge/my-app/variables \
-H "X-Project-Env: dev" \
-H "Authorization: Bearer <token>"
# Update another app's variables — requires `manage forge`
curl -X PATCH https://api.ptkl.app/v1/forge/my-app/variables \
-H "X-Project-Env: dev" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"WEBHOOK_URL": "https://staging.example.com/hook", "MAX_RETRIES": 3}'
The pathless /v1/forge/variables routes are only meaningful for an app token. Calling them with a
dashboard session or a plain API token returns 400 this endpoint requires a forge app token.
PATCH replaces the whole environment
Despite the verb, PATCH is not a merge — it replaces the entire variable set for the target
environment, so keys you omit are removed. The other environment is left untouched. Use
addVariable to change one key while keeping the rest.
The environment is determined by the X-Project-Env header (defaults to dev).
Accessing Variables in Services
Variables are available as the $variables global inside service scripts:
import '@ptkl/components/forge-service'
const webhookUrl = $variables.WEBHOOK_URL
const maxRetries = $variables.MAX_RETRIES || 3
const result = await http.post(webhookUrl, {
event: 'invoice.created',
data: input,
})
response.json({ delivered: true })
The platform injects the correct variable set (dev or live) based on which environment the service is executing in.
Accessing Variables in Lifecycle Scripts
Variables are also available in install and uninstall scripts:
import { Platform } from '@ptkl/sdk'
const platform = new Platform()
const endpoint = $variables.EXTERNAL_API_URL
if (endpoint) {
await http.post(endpoint, { event: 'app_installed' })
}
return { success: true }
Testing Lifecycle Scripts Locally
ptkl forge install and ptkl forge uninstall run the script on your machine, where the
app has no stored variables to read from. Supply them with --var (repeatable), --vars,
or --vars-file:
ptkl forge install --path ./my-app --env dev \
--var EXTERNAL_API_URL=https://staging.example.com \
--var DEBUG=true
See ptkl forge install for value parsing,
precedence, and the env-keyed file format.
Setting Variables During Installation
When an app is installed from the marketplace, initial variables can be provided as part of the installation payload. This allows marketplace apps to prompt for configuration during setup — for example, selecting a region or setting a default language.
Example: Environment-Specific Configuration
A common pattern is to use variables for values that differ between dev and live:
Dev variables:
Live variables:
Your service code stays the same — the platform injects the right set at runtime:
See Also
- Services — service execution model and scripting
- Lifecycle Scripts — install and uninstall scripts
- App Manifest —
ptkl.config.jsconfiguration reference