Skip to content

Commit acec880

Browse files
committed
feat(credential): add list operation with type/provider filters
1 parent 547bb29 commit acec880

13 files changed

Lines changed: 305 additions & 67 deletions

File tree

apps/docs/content/docs/en/blocks/credential.mdx

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,32 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
77
import { Image } from '@/components/ui/image'
88
import { FAQ } from '@/components/ui/faq'
99

10-
The Credential block selects a stored credential once and passes its ID to any downstream block that requires authentication. The credential is resolved securely at execution time — no secrets are exposed in the workflow state or logs.
10+
The Credential block has two operations: **Select Credential** picks a single stored credential and outputs its ID reference for downstream blocks; **List Credentials** returns all credentials in the workspace (optionally filtered by type) as an array for iteration.
1111

1212
<div className="flex justify-center">
1313
<Image
1414
src="/static/blocks/credential.png"
1515
alt="Credential Block"
16-
width={500}
17-
height={400}
16+
width={400}
17+
height={300}
1818
className="my-6"
1919
/>
2020
</div>
2121

2222
<Callout>
23-
The Credential block outputs a credential **ID reference**, not the secret itself. Downstream blocks receive the ID and resolve it securely during their own execution.
23+
The Credential block outputs credential **ID references**, not secrets. Downstream blocks receive the ID and resolve it securely during their own execution.
2424
</Callout>
2525

2626
## Configuration Options
2727

28-
### Credential
28+
### Operation
29+
30+
| Value | Description |
31+
|---|---|
32+
| **Select Credential** | Pick one credential and output its reference — use this to wire a single credential into downstream blocks |
33+
| **List Credentials** | Return all workspace credentials as an array — use this with a ForEach loop |
34+
35+
### Credential (Select operation)
2936

3037
Select a credential from your workspace. The dropdown shows all credential types you have access to, grouped by category:
3138

@@ -35,20 +42,61 @@ Select a credential from your workspace. The dropdown shows all credential types
3542

3643
In advanced mode, paste a credential ID directly. You can copy a credential ID from your workspace's Credentials settings page.
3744

45+
### Type Filter (List operation)
46+
47+
Filter the returned credentials by type. Defaults to **All**.
48+
49+
| Value | Description |
50+
|---|---|
51+
| **All** | Return all credential types |
52+
| **OAuth** | Connected OAuth accounts only |
53+
| **Env Variables (Workspace)** | Workspace environment variables only |
54+
| **Env Variables (Personal)** | Personal environment variables only |
55+
| **Service Accounts** | Google service account keys only |
56+
57+
### Provider (List operation, OAuth only)
58+
59+
Further filter OAuth credentials by provider. Select one or more providers from the dropdown — only providers you have credentials for will appear. Leave empty to return all OAuth providers.
60+
61+
| Example | Returns |
62+
|---|---|
63+
| Gmail | Gmail credentials only |
64+
| Slack | Slack credentials only |
65+
| Gmail + Slack | Gmail and Slack credentials |
66+
3867
## Outputs
3968

40-
| Output | Type | Description |
41-
|---|---|---|
42-
| `credentialId` | `string` | The credential ID — pipe this into other blocks' credential fields |
43-
| `displayName` | `string` | Human-readable name (e.g. "waleed@company.com") |
44-
| `type` | `string` | `oauth` \| `env_workspace` \| `env_personal` \| `service_account` |
45-
| `providerId` | `string` | OAuth provider (e.g. `google`, `github`), empty for non-OAuth types |
69+
<Tabs items={['Select Credential', 'List Credentials']}>
70+
<Tab>
71+
| Output | Type | Description |
72+
|---|---|---|
73+
| `credentialId` | `string` | The credential ID — pipe this into other blocks' credential fields |
74+
| `displayName` | `string` | Human-readable name (e.g. "waleed@company.com") |
75+
| `type` | `string` | `oauth` \| `env_workspace` \| `env_personal` \| `service_account` |
76+
| `providerId` | `string` | OAuth provider (e.g. `google`, `github`), empty for non-OAuth types |
77+
</Tab>
78+
<Tab>
79+
| Output | Type | Description |
80+
|---|---|---|
81+
| `credentials` | `json` | Array of credential objects (see shape below) |
82+
| `count` | `number` | Number of credentials returned |
83+
84+
Each object in the `credentials` array:
85+
86+
| Field | Type | Description |
87+
|---|---|---|
88+
| `credentialId` | `string` | The credential ID |
89+
| `displayName` | `string` | Human-readable name |
90+
| `type` | `string` | Credential type |
91+
| `providerId` | `string` | OAuth provider ID, empty for non-OAuth |
92+
</Tab>
93+
</Tabs>
4694

4795
## Example Use Cases
4896

4997
**Shared credential across multiple blocks** — Define once, use everywhere
5098
```
51-
Credential (Google) → Gmail (Send) & Google Drive (Upload) & Google Calendar (Create)
99+
Credential (Select, Google) → Gmail (Send) & Google Drive (Upload) & Google Calendar (Create)
52100
```
53101

54102
**Environment switching** — Swap credentials without touching downstream blocks
@@ -62,8 +110,25 @@ Condition (env === 'prod') → Credential (Prod API Key) or Credential (Dev API
62110
Agent (Determine account) → Condition → Credential A or Credential B → Slack (Post)
63111
```
64112

113+
**Iterate over all Gmail accounts**
114+
```
115+
Credential (List, OAuth, Provider: Gmail) → ForEach Loop → Gmail (Send) using <loop.currentItem.credentialId>
116+
```
117+
118+
<div className="flex justify-center">
119+
<Image
120+
src="/static/blocks/credential-loop.png"
121+
alt="Credential List wired into a ForEach Loop"
122+
width={900}
123+
height={400}
124+
className="my-6"
125+
/>
126+
</div>
127+
65128
## How to wire a Credential block
66129

130+
### Select Credential
131+
67132
1. Drop a **Credential** block and select your credential from the picker
68133
2. In the downstream block, switch to **advanced mode** on its credential field
69134
3. Enter `<credentialBlockName.credentialId>` as the value
@@ -90,17 +155,28 @@ Agent (Determine account) → Condition → Credential A or Credential B → Sla
90155
</Tab>
91156
</Tabs>
92157

158+
### List Credentials
159+
160+
1. Drop a **Credential** block, set Operation to **List Credentials**
161+
2. Optionally set a **Type Filter** (e.g. OAuth only)
162+
3. Optionally select one or more **Providers** to narrow results (only your connected providers appear)
163+
4. Wire `<credentialBlockName.credentials>` into a **ForEach Loop** as the items source
164+
5. Inside the loop, reference `<loop.currentItem.credentialId>` in downstream blocks' credential fields
165+
93166
## Best Practices
94167

95168
- **Define once, reference many times**: When five blocks use the same Google account, use one Credential block and wire all five to `<credential.credentialId>` instead of selecting the account five times
96169
- **Outputs are safe to log**: The `credentialId` output is a UUID reference, not a secret. It is safe to inspect in execution logs
97170
- **Use for environment switching**: Pair with a Condition block to route to a production or staging credential based on a workflow variable
98171
- **Advanced mode is required**: Downstream blocks must be in advanced mode on their credential field to accept a dynamic reference
172+
- **Use List + ForEach for fan-out**: When you need to run the same action across all accounts of a type, List Credentials feeds naturally into a ForEach loop
173+
- **Narrow by provider**: Use the Provider multiselect to filter to specific services — only providers you have credentials for are shown
99174

100175
<FAQ items={[
101176
{ question: "Does the Credential block expose my secret or token?", answer: "No. The block outputs a credential ID (a UUID), not the actual secret or token. Downstream blocks receive the ID and resolve it securely in their own execution context. Secrets never appear in workflow state, logs, or the canvas." },
102177
{ question: "What credential types does it support?", answer: "All credential types: OAuth connected accounts, workspace environment variables, personal environment variables, and Google service accounts." },
103-
{ question: "How is this different from just copying a credential ID into advanced mode?", answer: "Functionally identical — both pass the same credential ID to the downstream block. The Credential block adds value when you need to use one credential in many blocks (change it once), or when you want to select between credentials dynamically using a Condition block." },
178+
{ question: "How is Select different from just copying a credential ID into advanced mode?", answer: "Functionally identical — both pass the same credential ID to the downstream block. The Credential block adds value when you need to use one credential in many blocks (change it once), or when you want to select between credentials dynamically using a Condition block." },
179+
{ question: "Can I list all credentials in my workspace?", answer: "Yes. Set the Operation to 'List Credentials'. You can optionally filter by type (OAuth, environment variables, service accounts). Wire the credentials output into a ForEach loop to process each credential individually." },
104180
{ question: "Can I use a Credential block output in a Function block?", answer: "Yes. Reference <credential.credentialId> in your Function block's code. Note that the function will receive the raw UUID string — if you need the resolved token, the downstream block must handle the resolution (as integration blocks do). The Function block does not automatically resolve credential IDs." },
105-
{ question: "What happens if the credential is deleted?", answer: "The block will throw an error at execution time: 'Credential not found or access denied'. Update the Credential block to select a valid credential before re-running." },
181+
{ question: "What happens if the credential is deleted?", answer: "The Select operation will throw an error at execution time: 'Credential not found'. The List operation will simply omit the deleted credential from the results. Update the Credential block to select a valid credential before re-running." },
106182
]} />
61.7 KB
Loading
16.4 KB
Loading

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/combobox/combobox.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,10 @@ interface ComboBoxProps {
5959
/** Configuration for the sub-block */
6060
config: SubBlockConfig
6161
/** Async function to fetch options dynamically */
62-
fetchOptions?: (
63-
blockId: string,
64-
subBlockId: string
65-
) => Promise<Array<{ label: string; id: string }>>
62+
fetchOptions?: (blockId: string) => Promise<Array<{ label: string; id: string }>>
6663
/** Async function to fetch a single option's label by ID (for hydration) */
6764
fetchOptionById?: (
6865
blockId: string,
69-
subBlockId: string,
7066
optionId: string
7167
) => Promise<{ label: string; id: string } | null>
7268
/** Field dependencies that trigger option refetch when changed */
@@ -135,7 +131,7 @@ export const ComboBox = memo(function ComboBox({
135131
setIsLoadingOptions(true)
136132
setFetchError(null)
137133
try {
138-
const options = await fetchOptions(blockId, subBlockId)
134+
const options = await fetchOptions(blockId)
139135
setFetchedOptions(options)
140136
} catch (error) {
141137
const errorMessage = error instanceof Error ? error.message : 'Failed to fetch options'
@@ -144,7 +140,7 @@ export const ComboBox = memo(function ComboBox({
144140
} finally {
145141
setIsLoadingOptions(false)
146142
}
147-
}, [fetchOptions, blockId, subBlockId, isPreview, disabled])
143+
}, [fetchOptions, blockId, isPreview, disabled])
148144

149145
// Determine the active value based on mode (preview vs. controlled vs. store)
150146
const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue
@@ -363,7 +359,7 @@ export const ComboBox = memo(function ComboBox({
363359
let isActive = true
364360

365361
// Fetch the hydrated option
366-
fetchOptionById(blockId, subBlockId, valueToHydrate)
362+
fetchOptionById(blockId, valueToHydrate)
367363
.then((option) => {
368364
if (isActive) setHydratedOption(option)
369365
})
@@ -378,7 +374,6 @@ export const ComboBox = memo(function ComboBox({
378374
fetchOptionById,
379375
value,
380376
blockId,
381-
subBlockId,
382377
isPreview,
383378
disabled,
384379
fetchedOptions,

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/dropdown/dropdown.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,10 @@ interface DropdownProps {
5252
/** Enable multi-select mode */
5353
multiSelect?: boolean
5454
/** Async function to fetch options dynamically */
55-
fetchOptions?: (
56-
blockId: string,
57-
subBlockId: string
58-
) => Promise<Array<{ label: string; id: string }>>
55+
fetchOptions?: (blockId: string) => Promise<Array<{ label: string; id: string }>>
5956
/** Async function to fetch a single option's label by ID (for hydration) */
6057
fetchOptionById?: (
6158
blockId: string,
62-
subBlockId: string,
6359
optionId: string
6460
) => Promise<{ label: string; id: string } | null>
6561
/** Field dependencies that trigger option refetch when changed */
@@ -160,7 +156,7 @@ export const Dropdown = memo(function Dropdown({
160156
setIsLoadingOptions(true)
161157
setFetchError(null)
162158
try {
163-
const options = await fetchOptions(blockId, subBlockId)
159+
const options = await fetchOptions(blockId)
164160
setFetchedOptions(options)
165161
} catch (error) {
166162
const errorMessage = error instanceof Error ? error.message : 'Failed to fetch options'
@@ -169,7 +165,7 @@ export const Dropdown = memo(function Dropdown({
169165
} finally {
170166
setIsLoadingOptions(false)
171167
}
172-
}, [fetchOptions, blockId, subBlockId, isPreview, disabled])
168+
}, [fetchOptions, blockId, isPreview, disabled])
173169

174170
/**
175171
* Handles combobox open state changes to trigger option fetching
@@ -430,7 +426,7 @@ export const Dropdown = memo(function Dropdown({
430426
let isActive = true
431427

432428
// Fetch the hydrated option
433-
fetchOptionById(blockId, subBlockId, valueToHydrate)
429+
fetchOptionById(blockId, valueToHydrate)
434430
.then((option) => {
435431
if (isActive) setHydratedOption(option)
436432
})
@@ -446,7 +442,6 @@ export const Dropdown = memo(function Dropdown({
446442
singleValue,
447443
multiSelect,
448444
blockId,
449-
subBlockId,
450445
isPreview,
451446
disabled,
452447
fetchedOptions,

0 commit comments

Comments
 (0)