Skip to content

CrowdLinker/Shortcut-Importer

Repository files navigation

Shortcut CSV Importer

A small local app that bulk-creates Shortcut stories from a CSV file. You paste your API token, pick a team, upload a CSV, map columns, review a dry-run preview, and import.

  • 7-step wizard: API key → team → CSV → column mapping → epic decisions → preview → import
  • Auto-resolves names → IDs for epics, iterations, workflow states, members (by email/mention/name), labels, and projects
  • For each unmatched epic, you choose Create or Skip before importing
  • Dry-run preview shows every story with warnings before anything is sent to Shortcut
  • API token is stored only in your browser (localStorage)

Why a proxy?

The Shortcut API does not allow cross-origin browser requests, so a fetch from the SPA directly to api.app.shortcut.com fails with a CORS error. To avoid that, the app calls a same-origin path /sc-api/* and a server-side rewrite forwards it to https://api.app.shortcut.com/api/v3/*. The browser only ever talks to the same origin; your token rides along on each request as the Shortcut-Token header.

The rewrite is configured in two places:

  • Local dev / previewvite.config.ts uses Vite's built-in proxy for both vite (dev) and vite preview.
  • Production (Vercel)vercel.json declares the same rewrite as a Vercel route.

Requirements

  • Bun ≥ 1.0 (or npm/pnpm) for install and the dev server.

Quick start

bun install
bun dev

Open http://localhost:5173.

Preview a production build locally

bun run build
bun run preview   # serves dist/ at http://localhost:4173 with the same /sc-api proxy

Deploy to Vercel

The repo is Vercel-ready — no extra configuration needed.

vercel        # interactive first-time deploy
vercel --prod # promote to production

Vercel auto-detects Vite (build command bun run build, output dist/). The vercel.json at the repo root declares the rewrite that proxies /sc-api/* to Shortcut's API. Your Shortcut token is never stored on Vercel — it stays in each user's browser localStorage and is sent as a header on every request.

How to use

  1. Get a Shortcut API token at https://app.shortcut.com/settings/account/api-tokens. Copy it.
  2. Open the app, paste the token into Step 1, click Fetch workspace. The app loads your groups, workflows, epics, iterations, members, projects, and labels.
  3. Step 2 — pick the team (group) the imported stories should belong to and the workflow whose states you'll reference.
  4. Step 3 — drag & drop a .csv file (first row is the header).
  5. Step 4 — map each Shortcut field to a column from your CSV. The app auto-guesses based on header names (e.g. Titlename, Story Typestory_type); change anything that's wrong, and set columns you don't need to — none —.
  6. Step 5 — for each epic referenced in the CSV that doesn't already exist in Shortcut, choose Create (the app will create it under the selected team) or Skip (the story will be imported with no epic).
  7. Step 6 — review the dry-run table. Rows with errors are skipped; rows with warnings still import (e.g. unrecognized owner, invalid estimate).
  8. Step 7 — click to start the import. Epics are created first, then stories one at a time, with live progress and a per-row link to each created story.

CSV format

A starter file with all supported columns is available from the upload step (Download sample CSV) or directly at http://localhost:5173/sample.csv while the dev server is running. Open it, fill in your stories, and upload it back into the app.

The CSV must have a header row. Column names can be anything — you'll map them in Step 4. Empty rows are ignored. Available fields:

Field Notes
name Required. Story title.
description Markdown allowed.
story_type feature (default), bug, or chore.
estimate Integer.
epic Match by name. Unmatched values prompt you in Step 5.
iteration Match by name.
workflow_state Match by name within the workflow chosen in Step 2.
labels Comma-separated. New labels are created automatically by Shortcut.
owners Comma-separated emails or @mention_names.
requested_by Single email or @mention_name.
tasks Semicolon- or newline-separated.
external_id Free-form string.
deadline ISO date (2026-08-15) or anything Date parses.
project Match by name.

A minimal example:

name,story_type,epic,estimate,owners,labels
Implement onboarding tour,feature,Onboarding,3,alice@example.com,"frontend,onboarding"
Fix sign-up email typo,bug,,1,bob@example.com,bug

How it works

The same content is also available inside the app via the Help button in the header.

What happens when a cell is empty

An empty cell (or a column you didn't map) is treated identically: the field is simply not sent to Shortcut, and Shortcut applies whatever default it has.

Empty field Effect
name Row is skipped with an error. (The only required field.)
story_type Shortcut defaults to feature.
workflow_state Story uses the default state of your selected workflow.
requested_by Defaults to the owner of the API token.
epic No epic, and you're not prompted for it in Step 5.
anything else Field is omitted; story imports without it.

Whole-row empty CSV lines are dropped silently before any of this.

How names get resolved to IDs

The CSV uses human-readable names; the app resolves them to Shortcut IDs against the workspace it loaded in Step 1.

  • Epic — case-insensitive name match. Unmatched names go to Step 5 where you decide create (epic is created under the team you picked in Step 2 before stories are imported) or skip (story imports with no epic).
  • Iteration — name match. Unmatched → row warning, no iteration set.
  • Workflow state — matched by name within the workflow you picked in Step 2. Unmatched → row warning, workflow default state used.
  • Owners / Requested by — matched against email, @mention_name, or full name. Each entry is resolved independently — unresolved entries become row warnings.
  • Project — name match. Unmatched → row warning, dropped.
  • Labels — passed through by name; Shortcut auto-creates any new ones.

Errors vs warnings

The Step 6 preview classifies every row before anything is sent:

  • Error — the row is skipped entirely. Today this only happens when name is missing.
  • Warning — the row still imports, but a single field is dropped or replaced with a default (e.g. unrecognized owner, invalid estimate, unknown workflow state, invalid date).

Both error and warning details are listed per row in the preview, so nothing is hidden until import.

Validation rules per field

Field Validation
story_type Must be feature, bug, or chore. Anything else → warning, falls back to feature.
estimate Must parse as a number. Otherwise → warning, dropped.
deadline Must parse as a Date. Otherwise → warning, dropped.

Rate limits and import speed

Shortcut limits the API to about 200 requests per minute. Imports run sequentially — one story per request — so 500 stories take about three minutes. If a single story fails, the rest of the import continues; failures are reported per row at the end.

Token & data

  • Your token is kept in localStorage in your browser.
  • The browser only calls a same-origin path (/sc-api/*); a server-side rewrite forwards the request — token header and body — to api.app.shortcut.com. The rewrite is Vite's proxy in development and Vercel's rewrites rule in production. Neither stores the token.
  • Nothing is sent to any third party.

Scripts

bun dev          # Vite dev server with proxy at http://localhost:5173
bun run build    # Type-check + bundle to dist/
bun run preview  # Serve the built dist/ at http://localhost:4173 (proxied)
bun run lint     # ESLint

Project layout

src/
  api/shortcut.ts    Shortcut API client (calls /sc-api/* — proxied)
  api/types.ts       Shortcut payload/response types
  components/        Each wizard step is one file
  lib/csv.ts         Papaparse wrapper
  lib/mapping.ts     Auto-guesses CSV header → Shortcut field
  lib/resolve.ts     Resolves names → IDs and builds story payloads
  types.ts           Field list and labels for the UI
vite.config.ts       Dev + preview proxy: /sc-api → api.app.shortcut.com/api/v3
vercel.json          Vercel rewrite that does the same thing in production

For a deeper walkthrough see docs/:

Notes

  • Shortcut rate-limits the API at 200 requests/minute. Imports run sequentially, one story per request, so a 500-story CSV takes ~3 minutes.
  • The token is sent on every API call from the browser to the same-origin path, then rewritten upstream to Shortcut. Neither Vite (dev) nor Vercel (prod) logs request bodies by default.
  • If a story fails to create, the rest of the import continues — failures are shown per row at the end.

About

Shortcut CSV Importer using API token for workspace

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors