Skip to content

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:

entitlements: ['manage_variables forge'],

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:

{
    "WEBHOOK_URL": "https://staging.example.com/hook",
    "DEFAULT_LOCALE": "en_US",
    "DEBUG": true
}

Live variables:

{
    "WEBHOOK_URL": "https://example.com/hook",
    "DEFAULT_LOCALE": "sr",
    "DEBUG": false
}

Your service code stays the same — the platform injects the right set at runtime:

const locale = $variables.DEFAULT_LOCALE || 'en_US'

See Also