Why
A custom block's input params can be a text input, checkbox, dropdown, array editor, or an integration selector. The integration selector landed in #259: a param declared as integration_selector shows up inside the integration picker on the block's own canvas, and picking it writes param:<name>, so the concrete integration is chosen by whoever places the block. That is what makes a custom block reusable across projects without baking a connection id into it.
App config has no equivalent, and it is the other half of the same problem. Anything a block needs that is environment, not logic — a signing secret, an API key, a base URL, a feature flag — has to be typed into the block's graph as a literal today.
The e2e fixture added alongside this (testing/e2e/blocks/jwt-ops.json) makes the gap concrete: it hardcodes "e2e-custom-block-secret" in two JS blocks, because there is no way to say "the caller decides which config key holds the secret". A test can shrug at that. A real block cannot — the secret would live in the block's source, shared with every project that installs it.
What
An app_config_selector input param type: a param whose value is an app config key, chosen by the caller in the block's settings panel.
Sketch, to be argued with rather than followed:
- Portal, authoring — a new type in
InputParamsEditor alongside the existing five.
- Portal, calling — the settings panel renders a picker of the project's app config keys, the same way
BlockIntegrationField renders integrations. It writes param:<name> like every other param.
- Runtime — nothing new is obviously required.
param: already resolves from the invocation ($state.params), and getConfig(key) is already project-scoped in the context vars. The block's JS would read getConfig(params.secretKey). Worth confirming that reads as intended before adding machinery for it.
The open question is whether the param should carry the key (block does getConfig(params.secretKey)) or the resolved value (block does params.secret). The key is more honest — it keeps resolution project-scoped at call time and never puts a secret in a job payload — but it costs a getConfig call the author has to remember. Resolving eagerly is friendlier and leaks further. Worth deciding deliberately.
Notes
- Unlike integrations, app config has no consumer block: it is only ever read from JS. So this is a param type plus a picker, not a change to any block's settings.
- Same scoping rule as the integration selector: this belongs to the custom block chain only. A route canvas has no caller to resolve it.
- Once it lands, drop the hardcoded secret from
blocks/jwt-ops.json and let the fixture prove the path end to end.
Why
A custom block's input params can be a text input, checkbox, dropdown, array editor, or an integration selector. The integration selector landed in #259: a param declared as
integration_selectorshows up inside the integration picker on the block's own canvas, and picking it writesparam:<name>, so the concrete integration is chosen by whoever places the block. That is what makes a custom block reusable across projects without baking a connection id into it.App config has no equivalent, and it is the other half of the same problem. Anything a block needs that is environment, not logic — a signing secret, an API key, a base URL, a feature flag — has to be typed into the block's graph as a literal today.
The e2e fixture added alongside this (
testing/e2e/blocks/jwt-ops.json) makes the gap concrete: it hardcodes"e2e-custom-block-secret"in two JS blocks, because there is no way to say "the caller decides which config key holds the secret". A test can shrug at that. A real block cannot — the secret would live in the block's source, shared with every project that installs it.What
An
app_config_selectorinput param type: a param whose value is an app config key, chosen by the caller in the block's settings panel.Sketch, to be argued with rather than followed:
InputParamsEditoralongside the existing five.BlockIntegrationFieldrenders integrations. It writesparam:<name>like every other param.param:already resolves from the invocation ($state.params), andgetConfig(key)is already project-scoped in the context vars. The block's JS would readgetConfig(params.secretKey). Worth confirming that reads as intended before adding machinery for it.The open question is whether the param should carry the key (block does
getConfig(params.secretKey)) or the resolved value (block doesparams.secret). The key is more honest — it keeps resolution project-scoped at call time and never puts a secret in a job payload — but it costs agetConfigcall the author has to remember. Resolving eagerly is friendlier and leaks further. Worth deciding deliberately.Notes
blocks/jwt-ops.jsonand let the fixture prove the path end to end.