@notionhq/apps 0.0.28 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -3
- package/skills/connections/SKILL.md +3 -0
- package/AGENTS.md +0 -40
- package/docs/BUILD.md +0 -268
- package/docs/CONNECTIONS.md +0 -213
- package/docs/workflow-inputs.md +0 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notionhq/apps",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"description": "An SDK for building workflow apps for Notion",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -8,10 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"skills/",
|
|
11
|
-
"AGENTS.md",
|
|
12
11
|
"dist/",
|
|
13
12
|
"src/",
|
|
14
|
-
"docs/",
|
|
15
13
|
"LICENSE.md",
|
|
16
14
|
"README.md"
|
|
17
15
|
],
|
|
@@ -6,6 +6,9 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
# Workflow connections
|
|
8
8
|
|
|
9
|
+
For calendar functionality, use only `connections.calendar`. Do not use another
|
|
10
|
+
connection provider or connection as a calendar integration.
|
|
11
|
+
|
|
9
12
|
Import `{ workflow }` from `@notionhq/apps` and
|
|
10
13
|
`connections` from `@notionhq/apps/workflow`. Connections are not root exports.
|
|
11
14
|
Declare requirements on the workflow, then use the corresponding typed client
|
package/AGENTS.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Agent instructions
|
|
2
|
-
|
|
3
|
-
Before creating, modifying, or troubleshooting an App capability, read the
|
|
4
|
-
matching skill:
|
|
5
|
-
|
|
6
|
-
- [Workflows](./skills/workflow/SKILL.md)
|
|
7
|
-
- [Connections](./skills/connections/SKILL.md)
|
|
8
|
-
- [Database syncs](./skills/sync/SKILL.md)
|
|
9
|
-
- [Custom blocks](./skills/custom-blocks/SKILL.md)
|
|
10
|
-
- [Notion as Code](./skills/notion-as-code/SKILL.md)
|
|
11
|
-
|
|
12
|
-
Resolve these links relative to this package directory. Check the installed SDK
|
|
13
|
-
declarations for current API details.
|
|
14
|
-
|
|
15
|
-
## Pull requests for SDK changes
|
|
16
|
-
|
|
17
|
-
For a change to the public SDK, show the user-facing code change in the pull
|
|
18
|
-
request body. Put a short, complete example near the top:
|
|
19
|
-
|
|
20
|
-
````md
|
|
21
|
-
## User code
|
|
22
|
-
|
|
23
|
-
### Before
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
// Existing app code
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### After
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
// Updated app code
|
|
33
|
-
```
|
|
34
|
-
````
|
|
35
|
-
|
|
36
|
-
Use the same small example in both sections so reviewers can compare them
|
|
37
|
-
quickly. Include imports and the call that changed when they matter. Show
|
|
38
|
-
normal app code, not internal types, tests, generated files, or provisioning
|
|
39
|
-
output. Explain any required migration steps in plain language below the
|
|
40
|
-
example.
|
package/docs/BUILD.md
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
# App build process
|
|
2
|
-
|
|
3
|
-
`notion-apps build` discovers capability modules and produces:
|
|
4
|
-
|
|
5
|
-
- `dist/worker.js`, an ESM bundle containing the app's workflows and syncs.
|
|
6
|
-
- `dist/manifest.json`, the static capability and resource manifest.
|
|
7
|
-
- `dist/provisioning.json`, only when the app declares Notion-as-Code resources.
|
|
8
|
-
|
|
9
|
-
## Project convention
|
|
10
|
-
|
|
11
|
-
Each top-level TypeScript file under a capability directory must default-export the
|
|
12
|
-
corresponding capability. The filename becomes its key:
|
|
13
|
-
|
|
14
|
-
| Directory | Default export |
|
|
15
|
-
| ------------------- | ------------------ |
|
|
16
|
-
| `src/workflows/` | `workflow(...)` |
|
|
17
|
-
| `src/syncs/` | `sync(...)` |
|
|
18
|
-
| `src/customBlocks/` | `customBlock(...)` |
|
|
19
|
-
|
|
20
|
-
```text
|
|
21
|
-
my-app/
|
|
22
|
-
├── src/
|
|
23
|
-
│ ├── notion.ts
|
|
24
|
-
│ ├── workflows/
|
|
25
|
-
│ │ └── onPageCreated.ts
|
|
26
|
-
│ └── lib/
|
|
27
|
-
│ └── processPage.ts
|
|
28
|
-
├── .notion/
|
|
29
|
-
└── dist/
|
|
30
|
-
├── worker.js
|
|
31
|
-
└── manifest.json
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Files elsewhere under `src/` are ordinary modules and enter the build only when imported
|
|
35
|
-
by a capability. Capability discovery does not recurse into subdirectories.
|
|
36
|
-
Notion-as-Code declarations may be inline or in an imported helper. Use a local
|
|
37
|
-
module such as `src/workflows/myWorkflow/lib/notion.ts` for resources owned by one
|
|
38
|
-
capability, and `src/notion.ts` for app-wide declarations or resources without a
|
|
39
|
-
natural capability owner.
|
|
40
|
-
|
|
41
|
-
## Pipeline
|
|
42
|
-
|
|
43
|
-
1. Discover top-level files in the capability directories.
|
|
44
|
-
2. Generate `.notion/entry.ts` with workflow and sync imports and a dispatcher.
|
|
45
|
-
3. Bundle the app's code into `dist/worker.js`, leaving npm packages external.
|
|
46
|
-
4. Evaluate custom-block declarations separately. Blocks do not enter `worker.js`.
|
|
47
|
-
5. Evaluate worker and custom-block capabilities in a separate build-only metadata bundle to record
|
|
48
|
-
Notion-as-Code declarations. Validate workflow access references against those
|
|
49
|
-
declarations before writing the manifest and optional provisioning artifact.
|
|
50
|
-
6. Emit `dist/manifest.json` and, when declarations exist, `dist/provisioning.json`.
|
|
51
|
-
|
|
52
|
-
Capability modules must therefore be importable without secrets or network access.
|
|
53
|
-
Read required environment variables and make requests inside handlers or workflow
|
|
54
|
-
steps, not at module scope.
|
|
55
|
-
|
|
56
|
-
## Declaring Notion-as-Code databases
|
|
57
|
-
|
|
58
|
-
Database declarations use explicit resource IDs and keyed schemas. For a single
|
|
59
|
-
data source, supply the database ID as the first argument and the distinct data
|
|
60
|
-
source ID in `dataSourceResourceId`:
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
import { notion } from "@notionhq/apps/notion-as-code";
|
|
64
|
-
|
|
65
|
-
const tasks = notion.database("tasks-db", {
|
|
66
|
-
dataSourceResourceId: "tasks-source",
|
|
67
|
-
name: "Tasks",
|
|
68
|
-
schema: {
|
|
69
|
-
Name: { type: "title", resourceId: "tasks-name" },
|
|
70
|
-
Effort: { type: "text", resourceId: "tasks-effort" },
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Here `name` is the single-source shorthand: it supplies both the database and
|
|
76
|
-
data-source display names. The source handle is `tasks.dataSource`; pass it to
|
|
77
|
-
`sync({ dataSource, ... })` from `@notionhq/apps`.
|
|
78
|
-
|
|
79
|
-
For multiple sources, use `datasources` instead of the top-level `schema`:
|
|
80
|
-
|
|
81
|
-
````ts
|
|
82
|
-
const work = notion.database("work-db", {
|
|
83
|
-
name: "Work",
|
|
84
|
-
datasources: {
|
|
85
|
-
Tasks: {
|
|
86
|
-
resourceId: "work-tasks-source",
|
|
87
|
-
schema: {
|
|
88
|
-
Name: { type: "title", resourceId: "work-tasks-name" },
|
|
89
|
-
Effort: { type: "text", resourceId: "work-tasks-effort" },
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
Projects: {
|
|
93
|
-
resourceId: "work-projects-source",
|
|
94
|
-
schema: {
|
|
95
|
-
Name: { type: "title", resourceId: "work-projects-name" },
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
The keys in `datasources` are the source names, so the handles are indexed by
|
|
102
|
-
those names: `work.datasources.Tasks` and `work.datasources.Projects`. Nested
|
|
103
|
-
data-source configs do not accept `name`. The top-level database `name` remains
|
|
104
|
-
supported and names the database; in a single-source declaration it also
|
|
105
|
-
supplies the source display name. Property configs do not accept `name`; each
|
|
106
|
-
schema key is always the property's name. Typed property access uses
|
|
107
|
-
`work.datasources.Tasks.schema.Effort`; sync primary keys and row properties use
|
|
108
|
-
the string/object key `"Effort"`.
|
|
109
|
-
|
|
110
|
-
Every database, source, and property ID remains explicit and must be unique
|
|
111
|
-
across the provisioning declarations. No IDs are generated from keys or names.
|
|
112
|
-
Preserve explicit IDs when changing authoring keys or display names.
|
|
113
|
-
|
|
114
|
-
Page and teamspace handles accept the same forms through
|
|
115
|
-
`page.addDatabase("database-id", args)` and `teamspace.addDatabase("database-id", args)`.
|
|
116
|
-
Top-level declarations also accept
|
|
117
|
-
`parent: { type: "resourceId", resourceId: "parent-id" }`; omitting it retains the
|
|
118
|
-
private Apps workspace parent. A database must declare at least one data source
|
|
119
|
-
or at least one view. A linked-only declaration may omit `datasources`, or use
|
|
120
|
-
`datasources: {}`, only with a nonempty `views` list. The empty forms `{}`,
|
|
121
|
-
`{ datasources: {} }`, and `{ datasources: {}, views: [] }` are rejected; widened
|
|
122
|
-
or dynamic maps and arrays are checked at runtime. The single-source `schema` and
|
|
123
|
-
multi-source `datasources` forms cannot be combined.
|
|
124
|
-
|
|
125
|
-
Here is a linked-only database whose typed table view references an explicit
|
|
126
|
-
data source resource ID:
|
|
127
|
-
|
|
128
|
-
```ts
|
|
129
|
-
const linked = notion.database("work-linked-db", {
|
|
130
|
-
views: [
|
|
131
|
-
{
|
|
132
|
-
resourceId: "work-issues-table",
|
|
133
|
-
type: "table",
|
|
134
|
-
dataSourceResourceId: "issues-source",
|
|
135
|
-
properties: [{ property: "issue-title", visible: true }],
|
|
136
|
-
},
|
|
137
|
-
],
|
|
138
|
-
});
|
|
139
|
-
````
|
|
140
|
-
|
|
141
|
-
The build converts these declarations to the existing serialized database
|
|
142
|
-
intents: `dataSources` and `properties` remain arrays with exact resource IDs
|
|
143
|
-
and names from the authoring keys. Authoring-only `schema`, `datasources`, and
|
|
144
|
-
database-level `dataSourceResourceId` fields do not leak into the serialized
|
|
145
|
-
database intent. The provisioning JSON envelope and server API are unchanged.
|
|
146
|
-
|
|
147
|
-
## Provisioning artifact and deployment
|
|
148
|
-
|
|
149
|
-
The optional provisioning artifact uses `$schema: "notion:apps-provisioning:v1"`,
|
|
150
|
-
`version: 1`, and an `intents` array. These declarations are build metadata, not
|
|
151
|
-
provisioning operations executed by the deployed Worker.
|
|
152
|
-
|
|
153
|
-
After a successful build, the artifact reflects the current declarations. If no
|
|
154
|
-
declarations remain, the build removes any previous `dist/provisioning.json`
|
|
155
|
-
rather than uploading stale intents or emitting an empty artifact. This also
|
|
156
|
-
works when rebuilding into a reused `dist/` directory. Removing the artifact does
|
|
157
|
-
not delete previously provisioned Notion resources or reset their cloud state.
|
|
158
|
-
Do not deploy the output of a failed build.
|
|
159
|
-
|
|
160
|
-
Build and coordination modes use the existing CLI arguments:
|
|
161
|
-
|
|
162
|
-
| Command | Build location | Deployment coordination |
|
|
163
|
-
| ------------------------------------- | ------------------------------------------------ | ----------------------------- |
|
|
164
|
-
| `ntn apps deploy` | Cloud sandbox, using the project's installed SDK | Server (`workersBuildWorker`) |
|
|
165
|
-
| `ntn apps deploy --local-build --yes` | Local `notion-apps build` | CLI, as on `main` |
|
|
166
|
-
|
|
167
|
-
Cloud builds do not require the SDK or Node.js on the invoking machine. The
|
|
168
|
-
uploaded project must declare its SDK dependency and a `build` script that invokes
|
|
169
|
-
`notion-apps build`; the cloud runner uses the existing `npm run build` or
|
|
170
|
-
`pnpm run build` command, not a separate SDK command. The CLI creates an App-linked
|
|
171
|
-
Worker using `workersCreateWorker` with `createApp: true`, or updates the existing
|
|
172
|
-
Worker, then uploads source and calls `workersBuildWorker` with the Worker ID.
|
|
173
|
-
For App-linked Workers, the build endpoint coordinates capability registration,
|
|
174
|
-
Notion-as-Code provisioning, database attachment, workflow binding resolution,
|
|
175
|
-
and workflow permission reconciliation before reporting build success.
|
|
176
|
-
It returns the normal build result and run ID, not provisioning state. No separate
|
|
177
|
-
Apps deployment endpoint is required.
|
|
178
|
-
|
|
179
|
-
For App-linked Workers, the Notion-as-Code build phase prepares a separate
|
|
180
|
-
upload target, like the manifest target, for optional `dist/provisioning.json`.
|
|
181
|
-
The cloud runner uploads the file only if the build emits it; an upload failure
|
|
182
|
-
fails the build. The provisioning hooks read the JSON directly, not from the Worker
|
|
183
|
-
bundle. Provisioning JSON is limited to 5 MiB; a missing object means there are no
|
|
184
|
-
provisioning declarations, while other read errors or invalid JSON fail deployment.
|
|
185
|
-
|
|
186
|
-
This upload is a retained, deployment-scoped object in the existing Workers S3
|
|
187
|
-
bucket. The coordinator leaves it in storage after successful or failed deployment
|
|
188
|
-
attempts. It records build declarations, not the durable installation-owned
|
|
189
|
-
Notion-as-Code resource mappings and state.
|
|
190
|
-
Ordinary Worker deployment APIs, responses, and storage lifecycle are unchanged.
|
|
191
|
-
|
|
192
|
-
`--local-build` preserves the current CLI-coordinated setup. The CLI builds and
|
|
193
|
-
uploads the Worker bundle and manifest, uses the existing Notion-as-Code apply
|
|
194
|
-
flow and local state, and reconciles attachments. It reads the optional
|
|
195
|
-
`dist/provisioning.json` locally and does not upload it separately or invoke
|
|
196
|
-
the cloud build endpoint.
|
|
197
|
-
|
|
198
|
-
Workflows with nonempty `access` require cloud deployment. The local-build path
|
|
199
|
-
rejects them after the SDK build and before uploading or deploying the bundle.
|
|
200
|
-
|
|
201
|
-
Custom-block default bindings are stored in `manifest.json` as resource IDs and
|
|
202
|
-
resolved to live data source and property IDs during deployment. Live resource
|
|
203
|
-
bindings are not stored in SDK build output. Local deployments retain a
|
|
204
|
-
local state file and report `state_file`; cloud deployments keep installation-owned
|
|
205
|
-
resource mappings and state on the server. Cloud builds do not import local state
|
|
206
|
-
or return provisioning state to the CLI. Local and cloud state are independent:
|
|
207
|
-
switching between modes may recreate resources rather than reuse existing bindings.
|
|
208
|
-
|
|
209
|
-
Cloud failures never automatically retry locally. There is no rollback: code or
|
|
210
|
-
resources may already have changed when a deployment fails.
|
|
211
|
-
|
|
212
|
-
## Workflow access
|
|
213
|
-
|
|
214
|
-
`workflow({ access: { handbook: access.view(handbook) }, ... })`
|
|
215
|
-
declares access to a NaC resource and a named runtime binding. The manifest
|
|
216
|
-
contains only the alias and its symbolic `{ type, resourceId, level }`
|
|
217
|
-
requirement; live IDs are not build output.
|
|
218
|
-
Build validation rejects references missing from the current provisioning
|
|
219
|
-
declarations or having the wrong resource kind. `resourceId` is required because
|
|
220
|
-
it identifies the declaration used for this validation and binding.
|
|
221
|
-
|
|
222
|
-
After provisioning, cloud deployment resolves requirements against the successful
|
|
223
|
-
NaC apply result. It reconciles code-managed grants through the existing
|
|
224
|
-
Notion-module permissions and saves those permissions together with resolved
|
|
225
|
-
bindings on the workflow's worker module, keyed by capability and alias, in one
|
|
226
|
-
workflow transaction. Bindings are not authorization: the existing permissions
|
|
227
|
-
authorize `context.notion` calls. Redeployment updates or removes code-managed
|
|
228
|
-
grants as declarations change while preserving manually configured UI grants.
|
|
229
|
-
|
|
230
|
-
Execution reads the selected workflow configuration, not the latest NaC state,
|
|
231
|
-
validates the bindings against the capability requirements, and injects only that
|
|
232
|
-
capability's declared bindings. The SDK exposes typed, readonly `{ type, id }`
|
|
233
|
-
entries in `context.access`; UI-granted resources do not appear there. Use live
|
|
234
|
-
record IDs directly in `context.notion` calls for UI-granted resources; those
|
|
235
|
-
calls remain subject to the existing permissions. Missing or mismatched bindings
|
|
236
|
-
fail before the handler executes.
|
|
237
|
-
|
|
238
|
-
See the [workflow skill](../skills/workflow/SKILL.md#resources-created-with-the-app)
|
|
239
|
-
for supported resource types, access levels, and an authoring example.
|
|
240
|
-
|
|
241
|
-
## Manifest
|
|
242
|
-
|
|
243
|
-
The workflow-only manifest retains the platform's existing resource fields as empty arrays:
|
|
244
|
-
|
|
245
|
-
```json
|
|
246
|
-
{
|
|
247
|
-
"$schema": "notion:apps-manifest:v1",
|
|
248
|
-
"sdkVersion": "0.0.1",
|
|
249
|
-
"databases": [],
|
|
250
|
-
"xldbs": [],
|
|
251
|
-
"pacers": [],
|
|
252
|
-
"capabilities": [
|
|
253
|
-
{
|
|
254
|
-
"type": "workflow",
|
|
255
|
-
"key": "onPageCreated",
|
|
256
|
-
"config": {
|
|
257
|
-
"name": "Log New Pages",
|
|
258
|
-
"description": "Logs every page created in the workspace",
|
|
259
|
-
"triggers": [{ "type": "notion.page.created" }]
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
]
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
The platform invokes a workflow through the generated bundle's `run("workflow", key, event)`
|
|
267
|
-
dispatcher. Workflow results continue using the existing Notion output envelope, and runtime
|
|
268
|
-
metadata continues using the existing `NOTION_*` environment variables and `workerId` field.
|
package/docs/CONNECTIONS.md
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
# Workflow connections
|
|
2
|
-
|
|
3
|
-
Workflow connections declare services that a workflow needs to work properly. Connections require explicit authentication during setup before the workflow can use them. Each connection has a stable binding that links it to its configured credentials and permissions.
|
|
4
|
-
|
|
5
|
-
```ts
|
|
6
|
-
import { workflow } from "@notionhq/apps";
|
|
7
|
-
import { connections } from "@notionhq/apps/workflow";
|
|
8
|
-
import { triggers } from "@notionhq/apps/triggers";
|
|
9
|
-
|
|
10
|
-
export default workflow({
|
|
11
|
-
name: "List work calendars",
|
|
12
|
-
description: "List calendars available through the work connection",
|
|
13
|
-
triggers: [triggers.notionPageCreated()],
|
|
14
|
-
connections: { work: connections.calendar() },
|
|
15
|
-
handler: async (_event, context) => {
|
|
16
|
-
const calendars = await context.connections.work.listCalendars({});
|
|
17
|
-
console.log(calendars.accounts);
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
The object property name is the connection key. Provider factories do not take a key option. Keys must begin with a letter and contain at most 128 letters, numbers, underscores, or hyphens; `constructor` and `prototype` are reserved. A workflow can declare up to 100 connections.
|
|
23
|
-
|
|
24
|
-
## Trigger a workflow from a connection
|
|
25
|
-
|
|
26
|
-
Use a trigger callback to check connection keys against the workflow’s declared providers:
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
export default workflow({
|
|
30
|
-
name: "Support messages",
|
|
31
|
-
description: "Run when a message arrives in the configured support channel",
|
|
32
|
-
connections: { support: connections.slack() },
|
|
33
|
-
triggers: ({ triggers }) => [triggers.slackMessage({ connectionKey: "support" })],
|
|
34
|
-
handler: async (event, context) => {
|
|
35
|
-
console.log(event);
|
|
36
|
-
const user = await context.connections.support.findUserByEmail({
|
|
37
|
-
email: "person@example.com",
|
|
38
|
-
});
|
|
39
|
-
console.log(user);
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
The callback’s `triggers.slackMessage` accepts only Slack keys declared in `connections`. In this example, `"typo"` is a type error, and a Calendar connection named `"support"` would not satisfy a Slack trigger. Keys come from the object properties, so saving `const slack = connections.slack()` and declaring `{ support: slack }` still restricts the trigger to `"support"`.
|
|
45
|
-
|
|
46
|
-
The callback runs once when `workflow` is called. Its result is serialized as the ordinary trigger array, and the handler’s event type is inferred from those triggers. Returning a keyed trigger from an imported helper is checked too. Existing static trigger arrays remain supported and validate connection keys at runtime; use the callback for compile-time checking. Unbound triggers, including existing calls without `connectionKey`, keep their existing behavior.
|
|
47
|
-
|
|
48
|
-
Deployment creates a disabled trigger attached to that connection. Configure the account, channel or calendar, and enable the trigger through workflow setup before publishing. Redeploying preserves its configuration. The server checks the binding at publication and execution, so a trigger on another connection cannot invoke this declaration.
|
|
49
|
-
|
|
50
|
-
Calendar, Mail, Slack, Google Drive OAuth, and Discord currently have generated connection trigger helpers. Providers without registered public trigger events do not gain helpers merely by supporting actions. Existing helper calls without `connectionKey` keep their existing behavior. For `connections: { calendar: connections.calendar() }`, use `triggers.calendarEventCreated({ connectionKey: "calendar" })`.
|
|
51
|
-
|
|
52
|
-
Removing a declaration retains the configured trigger, but it can no longer invoke the capability unless another declaration allows it. Renaming a key creates a new connection and disabled trigger. Two keys can declare the same event type independently; repeating the same type and key is rejected.
|
|
53
|
-
|
|
54
|
-
## Use provider methods
|
|
55
|
-
|
|
56
|
-
Use the typed provider client to invoke a method:
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
const calendars = await context.connections.work.listCalendars({});
|
|
60
|
-
const user = await context.connections.support.findUserByEmail({
|
|
61
|
-
email: "person@example.com",
|
|
62
|
-
});
|
|
63
|
-
console.log(user?.displayName);
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Method inputs and results come from Tool Core's workflow projections. The SDK generates provider clients from those contracts, including any wire input or output transformations. Adding another registered provider generates its client from that provider's Tool Core methods.
|
|
67
|
-
|
|
68
|
-
The handler context exposes only the declared keys, each with its provider's client type. With `{ work: connections.calendar(), support: connections.slack() }`, `context.connections.work` is a Calendar client and `context.connections.support` is a Slack client. Missing keys and methods from the wrong provider are type errors. Omitting connections exposes no clients.
|
|
69
|
-
|
|
70
|
-
Two names can use the same provider:
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
connections: {
|
|
74
|
-
support: connections.slack(),
|
|
75
|
-
internal: connections.slack(),
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
`context.connections.support` and `context.connections.internal` use separately configured workflow bindings. Reusing a declaration across workflows does not share credentials.
|
|
80
|
-
|
|
81
|
-
For a separate declarations object, let TypeScript infer its type or use `satisfies WorkflowConnectionDeclarations`. A broad `WorkflowConnectionDeclarations` annotation loses the exact keys and providers. Reusable helpers can accept `WorkflowContext<typeof declarations>`.
|
|
82
|
-
|
|
83
|
-
### Migrating from connection arrays
|
|
84
|
-
|
|
85
|
-
Replace `connections: [connections.calendar({ key: "work" })]` with `connections: { work: connections.calendar() }`, then replace `context.connections.calendar("work")` with `context.connections.work`. For an old declaration with no explicit key, use the provider name as the object property to preserve its existing binding. Arrays and factory key options are no longer supported.
|
|
86
|
-
|
|
87
|
-
The SDK serializes the object to the existing manifest array of `{ key, type }` requirements. Keeping the same key and provider preserves the server binding.
|
|
88
|
-
|
|
89
|
-
## Supported providers and setup
|
|
90
|
-
|
|
91
|
-
The registry generates clients for Calendar, Mail, Slack, Google Drive OAuth, Discord, Cursor, Box, Confluence, Gmail, Google Calendar, Google Drive, Outlook, and Salesforce. Only eligible Tool Core methods are generated; this does not expose every operation in those services.
|
|
92
|
-
|
|
93
|
-
Each connection must be authenticated and granted access through the workflow’s setup flow before use. The server handles provider-specific authentication and checks that setup is complete. App code declares a provider under a binding key; declaring a connection never grants permissions.
|
|
94
|
-
|
|
95
|
-
Providers outside the supported list are not currently available as workflow connections. Unsupported declarations receive an unavailable-provider error.
|
|
96
|
-
|
|
97
|
-
### Mail
|
|
98
|
-
|
|
99
|
-
`connections.mail()` uses the personal Mail connection and selected accounts configured in workflow setup. It is separate from the admin-enabled `connections.gmail()` and `connections.outlook()` search connectors.
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
import { workflow } from "@notionhq/apps";
|
|
103
|
-
import { connections } from "@notionhq/apps/workflow";
|
|
104
|
-
|
|
105
|
-
export default workflow({
|
|
106
|
-
name: "Read unread mail",
|
|
107
|
-
description: "Search the configured mailbox when an email arrives",
|
|
108
|
-
connections: { inbox: connections.mail() },
|
|
109
|
-
triggers: ({ triggers }) => [triggers.mailEmailReceived({ connectionKey: "inbox" })],
|
|
110
|
-
handler: async (_event, context) => {
|
|
111
|
-
await context.step("Search unread mail", async () => {
|
|
112
|
-
const result = await context.connections.inbox.searchEmails({
|
|
113
|
-
userEmailAddress: "me@example.com",
|
|
114
|
-
query: "is:unread",
|
|
115
|
-
count: 20,
|
|
116
|
-
});
|
|
117
|
-
if (result.isError) throw new Error("Mailbox search failed");
|
|
118
|
-
return result;
|
|
119
|
-
});
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Mail exposes reads such as `searchEmails`, `viewThreadContent`, `readAttachment`, and `listLabels`, plus writes such as `sendNewEmail`, draft creation, and label updates. Search accepts `pageToken` for pagination. Results preserve the Mail tool response, including `content`, optional `structuredContent`, and `isError`; inspect the provider's response before using its contents. The server enforces selected accounts and action permissions on every call.
|
|
125
|
-
|
|
126
|
-
### Mail and Calendar write approval
|
|
127
|
-
|
|
128
|
-
Mail and Calendar write methods are available in configured workflows, including sending email and creating, updating, or canceling calendar events. Configuring and publishing the workflow authorizes unattended execution within the connection's permissions. The server automatically approves ordinary action confirmations for the workflow's granted Mail and Calendar connections; app code does not need to approve individual calls.
|
|
129
|
-
|
|
130
|
-
Account and calendar permissions still apply. Auto-approval does not grant access to an unselected mailbox, enable disallowed sending, or make a read-only calendar writable. Other confirmation requirements are not automatically approved. Direct agent calls retain their existing confirmation behavior.
|
|
131
|
-
|
|
132
|
-
## Regenerate provider clients
|
|
133
|
-
|
|
134
|
-
From `apps-sdk`, with a compatible sibling `notion-next` checkout and its mise toolchain installed:
|
|
135
|
-
|
|
136
|
-
```sh
|
|
137
|
-
pnpm exec tsx scripts/sync-connections.ts
|
|
138
|
-
pnpm exec tsx scripts/sync-connections.ts --check
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Pass a checkout path as the final argument if it is elsewhere. The script uses the server checkout’s mise toolchain because the repositories can use different Node versions. You can also run these commands directly from `notion-next`:
|
|
142
|
-
|
|
143
|
-
```sh
|
|
144
|
-
notion tool-core codegen-script-types --connections --path ../apps-sdk/src
|
|
145
|
-
notion tool-core codegen-script-types --connections --path ../apps-sdk/src --check
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### How generation works
|
|
149
|
-
|
|
150
|
-
1. The generator reads `src/shared/workflows/connectionProviders.ts` in `notion-next`. Each entry selects a provider; the server owns its authentication and readiness checks.
|
|
151
|
-
2. For each provider, it reads the corresponding workflow module's effects. It keeps effects backed by a Tool Core definition, exposed to script agents, and not excluded from the connection API. Mail and Calendar include writes because the workflow runtime can approve their action confirmations. Other providers include only read-only methods or methods without a confirmation policy. Server permission checks still apply; workflows do not yet support human confirmation steps.
|
|
152
|
-
3. It reads the exact Tool Core definition stored on each workflow effect projection, so providers with multiple tool versions use the contract selected by their module.
|
|
153
|
-
4. The existing script-type emitter renders the method's wire input and result types. It honors input field mappings and declared output projections, so the types describe the workflow endpoint contract.
|
|
154
|
-
5. It also reads each provider module’s trigger definitions and emits `connection-trigger-definitions.generated.ts`. The SDK trigger generator intersects these with the public `TriggerEventMap` to generate supported `connectionKey` options, descriptions, event types, and provider-scoped trigger creators. Internal events are not exposed.
|
|
155
|
-
6. It writes `<provider>.generated.ts` with types and thin client methods, plus `providers.generated.ts` with keyless declaration helpers, the provider-to-client type map, and the client factory. The runtime transport stays in `connections.ts`.
|
|
156
|
-
|
|
157
|
-
### Updating the SDK after a Tool Core change
|
|
158
|
-
|
|
159
|
-
Use compatible checkouts of both repositories.
|
|
160
|
-
|
|
161
|
-
1. Update the operation's Tool Core contract and workflow projection in `notion-next`. Changes to input/output schemas, effect exposure, or provider registration can require regeneration.
|
|
162
|
-
2. Run `pnpm exec tsx scripts/sync-connections.ts` and its `--check` variant from `apps-sdk`. This regenerates provider clients, trigger metadata, and trigger helpers together. After using the direct server command instead, run `pnpm run generate` in `apps-sdk` as well.
|
|
163
|
-
3. In `apps-sdk`, run `pnpm run typecheck`, `pnpm run lint`, `pnpm test`, `pnpm run build:types`, and `pnpm run build:js`.
|
|
164
|
-
4. Commit all changed generated provider files in the SDK PR, and link the corresponding server PR. Review wire compatibility and deploy server support before consumers rely on new methods.
|
|
165
|
-
|
|
166
|
-
For a new provider, add its server registry entry and supported module type, and ensure its workflow module implements connection setup, permission checks, and Tool Core-backed effects. Then regenerate. Registration alone does not create authentication or port legacy operations into Tool Core. Regeneration adds the provider’s declaration helper and typed methods to the SDK.
|
|
167
|
-
|
|
168
|
-
`--check` compares checked-in output with the selected local server checkout. SDK CI currently checks compilation and tests but does **not** check out `notion-next` or enforce cross-repository provider-codegen freshness. Synchronization is manual today. Generated files must not be edited by hand, and removed providers require removing their obsolete generated files as well.
|
|
169
|
-
|
|
170
|
-
This does not require app developers to import server code or install Tool Core at runtime.
|
|
171
|
-
|
|
172
|
-
## Runtime and rollout
|
|
173
|
-
|
|
174
|
-
The runtime automatically connects each declared key to the account configured during workflow setup. For example, `context.connections.work` uses the connection configured as `work`. App code does not manage connection IDs or credentials.
|
|
175
|
-
|
|
176
|
-
Provider method calls currently require the server's local/development environment and `public_api_runtime_sdk_tools` and `workers_call_function` gates. A personal access token alone does not enable these endpoints. Developer portal connection setup UI wiring is a separate follow-up. Renaming a requirement key creates a new binding; removing a requirement does not revoke or delete the server's existing configured module.
|
|
177
|
-
|
|
178
|
-
## Generic OAuth
|
|
179
|
-
|
|
180
|
-
Use `connections.oauth()` for an OAuth 2.0 provider without a generated provider client:
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
export default workflow({
|
|
184
|
-
name: "Read GitHub repositories",
|
|
185
|
-
description: "Read repositories using this workflow's GitHub authorization",
|
|
186
|
-
triggers: [triggers.scheduled()],
|
|
187
|
-
connections: {
|
|
188
|
-
github: connections.oauth({
|
|
189
|
-
authorizationEndpoint: "https://github.com/login/oauth/authorize",
|
|
190
|
-
tokenEndpoint: "https://github.com/login/oauth/access_token",
|
|
191
|
-
clientId: "your-oauth-app-client-id",
|
|
192
|
-
clientSecretEnv: "GITHUB_CLIENT_SECRET",
|
|
193
|
-
scope: "repo",
|
|
194
|
-
}),
|
|
195
|
-
},
|
|
196
|
-
handler: async (_event, context) => {
|
|
197
|
-
const token = await context.connections.github.accessToken();
|
|
198
|
-
const response = await fetch("https://api.github.com/user/repos", {
|
|
199
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
200
|
-
});
|
|
201
|
-
if (!response.ok) throw new Error(`GitHub returned ${response.status}`);
|
|
202
|
-
console.log(await response.json());
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Store the client secret in the app's Workers secrets under the name supplied by `clientSecretEnv`. The declaration contains the secret's name, not its value. Optional `authorizationParams` supports provider options such as `access_type: "offline"`; it cannot override state, callback, client ID, scope, or PKCE fields. Optional `accessTokenExpireMs` supplies a positive default expiry for providers that omit it.
|
|
208
|
-
|
|
209
|
-
Each workflow instance requires separate authorization, even when instances share the same app and declaration. The SDK reads only the access token bound to the current run and does not fall back to app-level `worker.oauth` tokens. The server refreshes tokens before execution; `accessToken()` does not make a refresh request during a long-running handler.
|
|
210
|
-
|
|
211
|
-
This SDK change requires the matching server OAuth implementation before deployment. In the workflow settings, find the declared connection in **Access**, alongside other provider connections. Choose **Connect** beside the declared key, authorize with the provider, and close the authorization window to refresh status. **Reconnect** replaces authorization for that workflow instance; If the client secret is missing, setup shows the secret name to configure. Setup requires full access to the app and workflow editor access. Store the client secret before connecting, and register the app OAuth callback URL with the provider. The SDK does not configure the provider’s OAuth app for you.
|
|
212
|
-
|
|
213
|
-
Generic OAuth provides authentication for your own API calls. It does not generate provider methods or provider triggers. Unlike the generated Tool Core clients, this helper is maintained directly in the SDK.
|
package/docs/workflow-inputs.md
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Workflow input values
|
|
2
|
-
|
|
3
|
-
Use `input` from `@notionhq/apps` to describe a `context.wait.forInput` form:
|
|
4
|
-
|
|
5
|
-
```ts
|
|
6
|
-
const response = await context.wait.forInput("Launch details", {
|
|
7
|
-
input: {
|
|
8
|
-
teams: input.multiSelect(["Design", "Engineering"]),
|
|
9
|
-
owners: input.person(),
|
|
10
|
-
window: input.date(),
|
|
11
|
-
attachments: input.files({ optional: true }),
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
- `teams` is an array of the declared options, typed as `("Design" | "Engineering")[]`.
|
|
17
|
-
- `owners` is a `string[]` of all selected user IDs, including when only one person
|
|
18
|
-
is selected. This replaces the previous scalar `string` response.
|
|
19
|
-
- `window` is `{ start: string; end?: string; timeZone?: string }`, not a string.
|
|
20
|
-
Date-only endpoints use `YYYY-MM-DD`; timed endpoints use local `YYYY-MM-DDTHH:mm`
|
|
21
|
-
together with the selected IANA `timeZone`. Ranges retain both endpoints.
|
|
22
|
-
The type is available as `WorkflowInputDate` from `@notionhq/apps/workflow`.
|
|
23
|
-
- `attachments` is a `string[]` with one URL or Notion attachment block ID per item,
|
|
24
|
-
in property order. Attachment display names are not returned as URLs.
|
|
25
|
-
|
|
26
|
-
Empty required multi-select, person, and file inputs keep the request waiting. Empty optional
|
|
27
|
-
inputs are omitted. Selections outside the declared options keep the request waiting
|
|
28
|
-
so the respondent can correct them.
|