Variables are named inputs for sources. You can define them using the variables: block, then use ${var:NAME} anywhere in source config or table definitions.
Use variables for values that should not be hard-coded into a source: API hosts, workspace ids, API keys, bearer tokens, or OAuth credentials. Compared with raw ${secret:} / ${env:} interpolation, variables give each source an explicit input contract and keep names scoped to the sources that use them.
Basic Example
Most sources only need a plain value and a secret:
variables:
API_BASE:
kind: variable
default: https://api.example.com
API_TOKEN:
kind: secret
input: EXAMPLE_API_TOKEN
sources:
- name: example
kind: http
config:
base_url: ${var:API_BASE}
token: ${var:API_TOKEN}API_BASE resolves from its default. API_TOKEN resolves from the configured secret chain using the EXAMPLE_API_TOKEN key. Pawrly substitutes those values only at the reference sites.
Declaring a variable does not inject it into a source. A variable matters only when the source references it with ${var:NAME}.
Declaration
Each variable has a kind:
- variable is non-secret configuration, such as a base URL, tenant id, or region. It may have a
defaultand atype(see Value Types), and its value can appear in introspection output. - secret is sensitive configuration, such as an API key, token, password, or OAuth credential. It is masked, may not have a
default, and is always an opaque string (notype).
Use description to explain what the user should provide during setup:
variables:
DATADOG_SITE:
kind: variable
default: datadoghq.com
description: Datadog site, for example datadoghq.eu
DATADOG_API_KEY:
kind: secret
input: DATADOG_API_KEY
description: Datadog API key with read accessNames may not start with __pawrly. A name that looks like a credential (token, secret, password, api_key, bearer, ...) must be declared as kind: secret.
Required and Optional
A variable is required by default: if a source references it and no value can be found (no env / secret value and no default), loading fails with an error telling you what to set. Set required: false to make the reference optional — an unresolved ${var:NAME} then resolves to null instead of failing the load.
variables:
PROXY_URL:
kind: variable
required: false # unset ⇒ null, not an errorThis works for both kinds — an optional kind: secret (for example, send no auth header when no token is provided) resolves to null when unset rather than erroring. A default always satisfies a reference, so required has no effect when a default is present. Use an optional reference only where the source config tolerates a null (typically an optional field).
Value Types
A kind: variable carries a type describing the shape of its value. It defaults to string, so existing declarations need no change. A resolved value is inlined with its real JSON type — a number becomes 100, a boolean true — not a quoted string, so typed source config receives real scalars.
type |
Inlined as | Notes |
|---|---|---|
string |
text | the default when type is omitted |
integer |
JSON integer | whole numbers only |
number |
JSON number | integer or floating point |
boolean |
JSON boolean | env values true/false, 1/0, yes/no, on/off |
enum |
the matching choice | requires a choices list |
variables:
PAGE_SIZE:
kind: variable
type: number
default: 100
VERIFY_SSL:
kind: variable
type: boolean
default: true
REGION:
kind: variable
type: enum
choices: [us, eu, ap]
default: us
description: Datacenter region
sources:
- name: api
kind: http
config:
region: ${var:REGION}
page_size: ${var:PAGE_SIZE}
verify_ssl: ${var:VERIFY_SSL}Rules:
typeis only valid onkind: variable; secrets are always opaque strings.choicesis required fortype: enumand rejected for any other type.- A
defaultmust match the declaredtype, and forenumit must be one of thechoices. - A value supplied through an environment variable (always text) is parsed into the declared type; a value that does not parse —
"maybe"for aboolean, or a string outside anenum'schoices— is a load error.
A ${var:NAME} reference is substituted only when the whole config value is the placeholder (page_size: ${var:PAGE_SIZE}). Typed inlining (a real number or boolean) applies only in that whole-value form.
Caveats
- Use the right type for the field. If a config field expects a string, keep the variable as
type: stringor omittype. Usenumber,integer, andbooleanonly where the source expects those real values. - Use variables inside
config:and table definitions. Avoid putting${var:}in top-level source fields such asraw_table: ${var:RAW}. Management commands read those fields before variables are resolved, so a placeholder string can fail where a boolean or number is expected.
Static Values
Static values are available as soon as Pawrly loads the config. Use them for hosts, tenant ids, API keys, and pasted bearer tokens.
Pawrly looks for a static value in this order:
- A value set with Pawrly using
pawrly variables setorpawrly source connect. This always wins over the values below. input. Forkind: variable, this reads an environment variable. Forkind: secret, this reads the configured secret chain (env->keyring->file), the same way${secret:NAME}does. If omitted,inputdefaults to the variable name.default- allowed only forkind: variable.- If nothing resolves: a load error for a required variable (the default), or
nullfor an optional one (required: false). See Required and Optional.
For non-secret variables, a stored value is a per-machine override. It lets you change something like REGION or PAGE_SIZE without editing config or environment variables. Pawrly checks the stored value against the variable's [type and choices](#value-types) when you set it.
To set a value, run either of:
pawrly source connect example # set up everything the source needs
pawrly variables set DATADOG_API_KEY # set one variable by name (secret or not)pawrly source connect <source> checks the variables used by that source. It prompts, without echo, for missing static secrets and starts OAuth for unconnected OAuth variables. By default, it only asks for values that still need setup. Add variable names to update specific values, or use --all to review everything.
pawrly source add runs the same setup after adding a source. In a terminal, it prompts for static secrets and offers to connect OAuth variables. In a non-interactive context, it prints the pawrly source connect command to run instead. Non-secret kind: variable values are not prompted; provide them through an environment variable or a default.
OAuth Values
Use OAuth when Pawrly should get the credential for you instead of reading a pasted value. OAuth is valid only on kind: secret.
variables:
GH_TOKEN:
kind: secret
oauth:
grant: { type: device_code }
endpoints:
device_authorization_url: https://github.com/login/device/code
token_url: https://github.com/login/oauth/access_token
client:
id: { default: my-public-client-id }
scopes:
scope:
delimiter: space
values: [repo, read:org]Interactive OAuth setup happens before queries. Connect the source that references the OAuth variable:
pawrly source connect <source>The refresh token is stored by Pawrly and is never written into config. During queries, Pawrly uses that stored token to refresh access. If the variable has not been connected yet, the query asks you to run pawrly source connect <source>.
pawrly source add also detects unconnected OAuth variables for a new source and offers to connect them.
OAuth Grants
Pawrly supports three grants:
client_credentialsis for machine-to-machine APIs. Pawrly gets a token without asking a user to approve anything.device_codeis for user approval when Pawrly cannot receive a browser callback. Pawrly prints a URL and code, and the user approves in a browser.authorization_codeis for providers that require a browser redirect. Pawrly opens the authorization flow, listens for the local redirect, exchanges the code, and stores the refresh token. Usepkce: requiredwhen the provider requires PKCE.
Pawrly ships no built-in OAuth client ids. Put a public client id in the spec with default, or read a per-user client id with input.
OAuth Examples
client_credentials:
variables:
SF_TOKEN:
kind: secret
oauth:
grant: { type: client_credentials }
endpoints:
token_url: https://login.example.com/oauth2/token
client:
id: { default: my-client-id }
secret: { input: SF_CLIENT_SECRET }
scopes:
scope:
delimiter: space
values: [api]device_code:
variables:
GH_TOKEN:
kind: secret
oauth:
grant: { type: device_code }
endpoints:
device_authorization_url: https://github.com/login/device/code
token_url: https://github.com/login/oauth/access_token
client:
id: { default: my-public-client-id }
scopes:
scope:
delimiter: space
values: [repo, read:org]authorization_code:
variables:
OKTA_TOKEN:
kind: secret
oauth:
grant: { type: authorization_code, pkce: required }
redirect:
uri: http://127.0.0.1/callback
endpoints:
authorization_url: https://example.okta.com/oauth2/v1/authorize
token_url: https://example.okta.com/oauth2/v1/token
client:
id: { input: OKTA_CLIENT_ID }
secret: { input: OKTA_CLIENT_SECRET }
scopes:
scope:
delimiter: space
values: [openid, offline_access]Redirects
authorization_code grants require a redirect block so Pawrly knows where to receive the browser callback:
redirect:
uri: http://127.0.0.1:5000/callbackFor local setup, use a loopback URL such as http://127.0.0.1/callback. If the URI has no port, Pawrly listens on any free port and sends that full callback URL to the provider during setup.
Only pin the port when the provider requires an exact callback URL:
redirect:
uri: http://127.0.0.1/callback
port: 5000
# port_mode: random|fixed optionalYou can also put the port directly in redirect.uri, such as http://127.0.0.1:5000/callback. Do not set the port in both places. redirect.port_mode defaults to random when no port is set and fixed when a port is set; set it explicitly only when you need to be precise.
Endpoint Discovery
Providers that publish an OpenID Connect discovery document let you skip the per-grant URLs. Set endpoints.discovery to the provider's .well-known/openid-configuration URL and Pawrly reads authorization_endpoint, device_authorization_endpoint, and token_endpoint from it the first time the variable is used.
variables:
OKTA_TOKEN:
kind: secret
oauth:
grant: { type: authorization_code, pkce: required }
redirect:
uri: http://127.0.0.1/callback
endpoints:
discovery: https://example.okta.com/.well-known/openid-configuration
client:
id: { input: OKTA_CLIENT_ID }
secret: { input: OKTA_CLIENT_SECRET }
scopes:
scope:
delimiter: space
values: [openid, offline_access]The discovery URL must be https (or http to a loopback host for local development). The document is fetched lazily and cached on disk for 24 hours. Any explicit endpoint you also set under endpoints overrides the discovered value, so you can use discovery for most URLs and pin one by hand.
Multiple Auth Methods
A secret can offer more than one way to get a value. For example, prefer OAuth but allow the user to paste an existing token:
variables:
GH_TOKEN:
kind: secret
methods:
- type: oauth
label: Connect with GitHub
grant: { type: device_code }
endpoints:
device_authorization_url: https://github.com/login/device/code
token_url: https://github.com/login/oauth/access_token
client:
id: { default: my-public-client-id }
- type: input
label: Paste token
input: GITHUB_TOKENWhen a variable offers more than one method, pawrly source connect <source> shows a menu using each method's label:
`GH_TOKEN` offers more than one method:
1) Connect with GitHub
2) Paste token
3) Skip
Choose [1-3]:Choosing the OAuth method starts the connect flow. Choosing the input method prompts for a value without echoing it.
A pasted value is stored by Pawrly and overrides OAuth at load time, so it takes effect immediately. To switch back to OAuth, run pawrly source connect <source> GH_TOKEN again and choose the OAuth method.
pawrly variables set GH_TOKEN is the non-interactive version of the paste path. It works for any secret that offers an input method, including a multi-method secret like the one above. An OAuth-only secret cannot be set this way; connect it with pawrly source connect.
Use the shorthand forms for one method:
input: ENV_KEYfor one static input.oauth: { ... }for one OAuth method.
Use methods: only when setup should offer a choice. A variable may use either a shorthand or methods:, not both.
Scopes
variables: can be declared in four places:
- Global - a top-level
variables:block, visible to every source. - Fragment file - a
variables:block in an included file, visible to that file's sources and nested includes. - Single-source file - a top-level
variables:block in a bare one-source file. - Source-local - a
variables:key inside one source, visible only to that source.
A source sees the variables along its include chain. Inner declarations shadow outer ones.
Variables use their own namespace, separate from ${secret:} and ${env:}. The same name in two unrelated sources refers to two independent variables. Two sources share a value only when both reference the same declaration, such as a global variable or one from a shared include.
Commands
pawrly variables list # show declared variables
pawrly variables set <name> # set one static variable by name (secret or non-secret)
pawrly source connect <source> # set up everything a source needs (secrets + OAuth)Storage
Pawrly stores values it manages itself, such as values set with pawrly variables set, secrets collected by pawrly source connect, non-secret overrides, and OAuth refresh tokens.
Values are stored per variable declaration, not just by name. That means two sources can both have a variable called TOKEN without colliding. This store is also separate from ${secret:NAME}, so raw secrets and declared variables stay in different namespaces.
Where a value lives depends on how Pawrly obtains it:
- Provided manually (
pawrly variables set) or set up for a source (pawrly source connect): Pawrly uses the OS keyring when it can. If no keyring is available, it uses encrypted files under<home>/variables/. The encryption key comes from$PAWRLY_TOKEN_KEYwhen set; otherwise Pawrly creates one and saves it to<home>/variables/keywith owner-only permissions. - Running on a server, CI, Docker, or another headless setup: Set
PAWRLY_NO_KEYRING=1for bothpawrly source connectand the engine. This makes both processes use the encrypted file store, which avoids session-only keyrings that one process can write but another cannot read. - Stored by Pawrly: These values are never written to config and are never stored as plaintext on disk. A stored value wins over the same variable's
input, environment value,default, or secret-chain value. Forkind: variable, this gives you a per-machine override for non-secret config. - Inherited from the environment or secret store via
input, or from adefault: Pawrly does not copy these into its own store. It reads them from the original source each time the config loads.
Inspecting Variables
Query system.variables to see what variables are declared and whether Pawrly can resolve them now:
SELECT source, key, kind, type, required, available
FROM system.variables;| Column | Meaning |
|---|---|
source |
global or the source that owns the variable |
key |
Variable name |
kind |
variable or secret |
type |
Value type. Secrets are always string. |
value |
Non-secret value. NULL for secrets and OAuth values. |
default_value |
Declared default, if any |
description |
Declared description, if any |
required |
Whether the value must be provided |
available |
Whether Pawrly can provide the value now |
Find variables that still need setup with:
SELECT key
FROM system.variables
WHERE NOT available;