<aside> <img src="/icons/square-three-fourths_gray.svg" alt="/icons/square-three-fourths_gray.svg" width="40px" />
Duplicate this Skill into your workspace so start using it
</aside>

Use this when building an Asana → Notion sync as a Notion Worker. It reproduces a proven setup: two related managed databases (Projects and Tasks), native Notion row icons carried over from Asana, person properties for assignees and owners, and hourly replace syncs.
Not for: writing back to Asana (this sync is one-way), or one-off Asana imports that don't need to stay current.
Notion CLI (ntn) authenticated. Check with ntn whoami; only run ntn login if that fails.
Node >= 22, npm >= 10.9.2.
Asana Personal Access Token from Asana's Developer Console → Create new token. Asana recommends PATs for single-user integrations and treats them like passwords.
Asana project GIDs — the numeric ID in each project URL:
<https://app.asana.com/1/><workspace>/project/1206617296084787/list/...
^^^^^^^^^^^^^^^^
ntn workers new asana-project-sync
cd asana-project-sync && npm install
Read .agents/skills/sync-guide/ (and api-pagination-patterns.md) before writing code. Asana uses offset pagination, which is covered there.
| Handle | Type | Purpose |
|---|---|---|
ASANA_PAT |
worker.credential() |
Bearer token injected into app.asana.com requests |
asanaProjects |
worker.database() |
Managed Projects target |
asanaTasks |
worker.database() |
Managed Tasks target, related to projects |
asanaApi |
worker.pacer() |
120 requests / 60s (Asana's limit is 150/min) |
asanaProjectsSync |
worker.sync() |
Hourly, replace mode |
asanaTasksSync |
worker.sync() |
Hourly, replace mode |
Declare the PAT as a credential, not a plaintext secret, so worker code can never read the raw value:
worker.credential("ASANA_PAT", {
network: [
{
domain: "app.asana.com",
transform: [{ headers: { Authorization: `Bearer ${CREDENTIAL_VALUE}` } }],
},
],
})
Project selection is ordinary config. Support both a comma-separated list and numbered slots, then merge and dedupe:
process.env.ASANA_PROJECT_GIDS // "123,456,789"
process.env.ASANA_PROJECT_GID_1 // one project per variable
Throw if no GIDs resolve — an empty list plus replace mode would wipe both databases.