From Visa Application
Use when the user wants help applying for any visa from any country — Schengen tourist (Italy, France, Germany, Spain, etc.), US B1/B2, UK visitor, Japan, Canada, China, Australia, or any other. Triggers on phrases like "I need to apply for a visa", "help me with my Schengen visa", "Italy visa application", "US B1/B2", "UK visitor visa", "Japan tourist visa", "Canada eTA", "Australian visa", mentions of VFS / TLScontact / BLS visa centres, consulate appointments, biometrics, or visa renewals. Walks the user from a one-line ask to a fully assembled, officer-ready Print Pack: brief Q&A → research current official requirements online (cross-validated against ≥2 sources, government sites preferred) → reuse or build a portable user profile → set up or reuse the application folder → generate cover letter, employment letter, filled application form, and printable checklist PDF → assemble numbered Print Pack. Maintains a profile so the next application is a one-day job, not a one-week job.
How this skill is triggered — by the user, by Claude, or both
Slash command
/visa-application:visa-applicationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A general-purpose workflow for assembling a complete, officer-ready visa application from any country to any country. Designed to be invoked once per application and reused across them — the user's reusable data is captured once and replayed forever.
A general-purpose workflow for assembling a complete, officer-ready visa application from any country to any country. Designed to be invoked once per application and reused across them — the user's reusable data is captured once and replayed forever.
Visa applications are high-stakes, high-paperwork, and rules drift. Most applicants either over-search and waste days, or under-search and get rejected on a single missing line. This skill standardises the workflow: capture the few things only the user knows, look up the rest from official sources, cross-validate, generate every document the consulate actually wants in the order they want it, persist what's reusable, and pick up the thread on subsequent invocations all the way through to granted-visa capture.
Accuracy is the principal currency. If a fact disagrees between two sources, surface the disagreement to the user — don't paper over it.
Whenever the user mentions applying for a visa to any country, even casually ("can you help me sort out my visa for Japan?"). Don't wait for them to ask for "the visa skill" — that's not a phrase they'll use.
The skill's first turn must look identical on every invocation. Three things happen in this exact order, in the same response:
Print the banner (visible — five-line pixel-style header in a fenced code block):
██████
█▒▒ █ visa-application v1.0 · MIT
█▒ █ any visa · any country · officer-ready in one session
█ ●▒█ by @Shadowhusky · github.com/Shadowhusky/visa-application
██████
Run Phase 0 searches (silent — tool calls, not narrated):
bash scripts/find-existing.sh profile
bash scripts/find-existing.sh folder "<destination from user msg, or 'visa'>"
ls -la ~/.claude/visa-profile.json ~/.claude/visa-history.json 2>/dev/null
Call the AskUserQuestion tool with either the cold-start 4-question set (if Phase 0 found no profile) or the warm-start single question (if Phase 0 found one).
No prose between steps. No "I'd be happy to help" preamble. No "Let me check…" narration. Banner → silent searches → interactive question.
The skill runs in nine phases in strict order. Do not skip Phase 0. Do not list options in prose when the AskUserQuestion tool can be used. The same invocation should produce the same workflow every time — that's the whole point of having a skill.
This is the second step of the activation ritual above. Already specified there. The result determines whether Phase 1 is the cold-start 4-question kickoff or the warm-start single question.
Always use the AskUserQuestion tool when asking the user anything in this skill. Never list options in prose like "1. Tourist 2. Business …" — that's what the structured tool is for, and the user's experience must be consistent across invocations.
The tool caps at 4 options per question with an auto-"Other" fallback. Choose options that cover ~80% of likely answers; the long tail goes to "Other".
Send one AskUserQuestion call with the questions below. Important: before deciding which to include, parse the user's initial message for facts already given. If they said "I need a Schengen visa to Italy from the UK, 5 days in late June", that's destination + origin + visa type + duration already answered — do NOT re-ask. Only include the questions whose answers are missing.
| # | Question | header | Options |
|---|---|---|---|
| 1 | Which country are you applying for a visa to? | Destination | Schengen area (Italy, France, Germany, Spain, NL…) · United States · United Kingdom · Canada / Australia / Japan / Other |
| 2 | Which country are you applying from (where you legally reside)? | Applying from | United Kingdom · United States · India · China |
| 3 | What type of visa? | Visa type | Tourist / Visitor · Business / Conference · Study / Work · Visit family / Transit / Other |
| 4 | Trip duration? | Duration | Under 1 week · 1–2 weeks · 2–4 weeks · Over 1 month |
If the user picks an "Other" or umbrella option, follow up with a single free-text question to clarify (e.g., "Which Schengen country specifically?" or "Which Asian country?"). Don't try to enumerate 27 Schengen members in the tool — let them type the country name.
If all four facts are already in the user's initial message, skip the cold-start questions entirely and confirm in one short sentence ("Got it — Italy Schengen tourist, ~5 days, applying from the UK. Moving on.") before proceeding to Phase 2.
After the four kickoff answers, ask one free-text follow-up for the specific dates (e.g., "What's the exact departure → return date range?"). Dates have unbounded answer space and aren't a good fit for the option tool.
Skip the 4-question kickoff entirely. The profile already has identity, residence, employment, banking, and prior-visa history. Send one AskUserQuestion:
| Question | header | Options |
|---|---|---|
| Profile found ({last_updated}). What would you like to do? | Next step | Continue existing application (only if a relevant folder was found) · Start new application to a different country · Update my profile (employer, address, etc.) · Something else |
Branch the rest of the workflow based on the answer:
application_status.html to figure out what's already done and what's outstanding. Jump to the first incomplete phase (could be appointment booking, document upload, form filling, or just the cross-check). Don't restart from Phase 1.~/.claude/visa-profile.json without starting any application workflow.If the AskUserQuestion call returns "User declined to answer", fall back to a single short free-text message: "No worries — just tell me in your own words: where are you going, where are you applying from, what kind of visa, and roughly when?" — and continue from their reply.
Phase 0 already told you whether a profile exists. Now handle each case:
Profile found: read the file, summarise the headline identity to the user in one sentence (via plain prose, not AskUserQuestion — this is a confirmation, not a question), and proceed to Phase 3. The "warm start" question in Phase 1 has already determined intent.
No profile found: capture data using the document intake flow below — never type-by-type Q&A unless the user prefers it. Write to ~/.claude/visa-profile.json after each successful extraction so progress isn't lost.
Most users have IDs, payslips, bank statements, hotel bookings, and flight confirmations as PDFs or images already. Treat every uploaded file as an opportunity to (a) extract structured data, (b) cross-check against the profile, and (c) file it into the application folder.
When a user pastes or attaches a file:
AskUserQuestion to let the user clarify. Common traps:
passport-bio.pdf, payslip-{YYYY-MM}.pdf, hotel-{city}.pdf.The user shouldn't have to retype data that's already on a document they have. The flow should feel like "drop the file, get a short summary back, confirm, move on".
Never dump a multi-question text wall. If you need 4 or more free-text answers from the user (remaining profile fields after document intake, visa-form-specific questions like parents' names / education history / travel history, etc.), generate an interactive HTML questionnaire instead.
When to use:
AskUserQuestion if multiple-choice).questionnaire.html in the application folder.How to generate:
templates/questionnaire.html:
QUESTIONNAIRE_TITLE — e.g., "US B1/B2 Visa — Additional Information"QUESTIONNAIRE_SUBTITLE — e.g., "Fill in the fields below. Green fields are pre-filled from your profile."OUTPUT_FILENAME — e.g., "ds160-answers.json"PREFILLED_JSON_PLACEHOLDER — JSON object of values already known from the profileFORM_SECTIONS_PLACEHOLDER — the dynamic fieldset blocks (see format below)find ~/Downloads -name "OUTPUT_FILENAME" -newer "{app-folder}/questionnaire.html" 2>/dev/null
visa-profile.json and/or the application-specific state.Fieldset format for FORM_SECTIONS_PLACEHOLDER:
<fieldset data-section="family">
<legend>Family</legend>
<div class="field">
<label for="father_name">Father — full name (pinyin)</label>
<input type="text" id="father_name" name="father_name" placeholder="e.g., ZHANG WEI">
</div>
<div class="field">
<label for="father_dob">Father — date of birth</label>
<input type="date" id="father_dob" name="father_dob">
</div>
</fieldset>
Use the right input type for each field: type="date" for dates, type="tel" for phone numbers, type="email" for email, <textarea> for multi-line answers (travel history, prior employment), <select> for fixed-choice fields. Use the class="field-row" wrapper to pair two short fields side-by-side (e.g., first name + last name, from-date + to-date).
Pre-population: every field the profile already has gets pre-filled (green tint in the UI). The user only types what's missing. If the profile already covers 60% of the fields, the form is 60% done before the user even opens it.
Each application gets its own folder. Always search first for an existing one the user may have started:
bash scripts/find-existing.sh folder "{destination}"
This searches the common iCloud Drive Travel/ and Documents tree, and prints any folder whose name matches *{destination}*, *visa*{destination}*, or *{destination}*visa*.
If a candidate folder is found: ask via AskUserQuestion (not prose):
| Question | header | Options |
|---|---|---|
| I found a folder that looks related to your {destination} application — use it? | Folder | Use this folder · Create a new one · Show me where it is first |
If no candidate is found OR the user chose "Create a new one": ask via AskUserQuestion:
| Question | header | Options |
|---|---|---|
| Where would you like the new application folder? | Location | ~/Documents/Visa Applications/ (default) · iCloud Drive · Desktop · Custom path |
Then create the folder with destination + year in the name. Don't create silently.
Inside the application folder, you'll later create:
{destination}-{year}/
├── Cover Letter.html ← opens in browser
├── Cover Letter.pdf ← PDF fallback / print
├── Employment Letter.html (if employed)
├── Employment Letter.pdf
├── Visa-application-form-FILLED.pdf (if portal exists)
│ or application_form_data.html + .pdf (Tier 4 fallback)
├── Checklist.html
├── Checklist.pdf
├── application_status.html ← running state (schema below)
├── Application Status.pdf
├── (user-provided documents the user drops in)
└── Print Pack/
├── 00 - CHECKLIST.pdf
├── 01 - …
└── …
Every skill-generated document has both .html (for browser viewing) and .pdf (for mobile / print). User-provided uploads (passport scans, bank statements, etc.) stay in their original format.
application_status.html schemaThis file is the source of truth for multi-session continuity. Phase 0 reads it via the folder search; Phase 1's "Continue" branch resumes from it; Phase 7 writes to it; Phase 8 reads it to decide which post-submission branch to enter. Use the templates/application-status.html template — substitute ALL_CAPS placeholders with real values. The result is a styled HTML page that opens in any browser and is still easy for the agent to parse via the Read tool.
The status badge uses one of: in_progress, submitted, awaiting_decision, granted, refused — set both the badge text and the CSS class (status-in_progress, etc.) to match.
Progress rows use the PROGRESS_ROWS_PLACEHOLDER marker. Each row is an <li> with class done or todo:
<li class="done">Research complete</li>
<li class="done">Profile loaded</li>
<li class="todo">Submitted at centre</li>
After substituting placeholders, also render the HTML to PDF via render-pdf.sh so the user has both formats:
bash scripts/render-pdf.sh "{application-folder}/application_status.html" "{application-folder}/Application Status.pdf"
Keep this file updated at the end of every phase that changes state. Re-write the whole file (not just append) so it remains a clean snapshot.
This is the part where accuracy matters most. Read references/research-protocol.md for the full protocol — the short version:
conslondra.esteri.it. Search "{destination} consulate {origin city} visa {type}".See references/known-portals.md for known online application portals (Italy e-applicationvisa.esteri.it, US ceac.state.gov DS-160, UK gov.uk/standard-visitor, etc.) and references/document-checklist.md for typical document lists by visa type — both as starting points, not gospel; always re-verify online.
The visa appointment is the timeline anchor. Every document has to be ready before this date, and most countries require the appointment to be booked before generating the application form (the form often references the appointment number). It's also a common user blind spot — they assume "the visa centre will fit me in next week" and discover slots are 4–6 weeks out. So the skill asks early.
Send one AskUserQuestion:
| Question | header | Options |
|---|---|---|
| Have you already booked your visa appointment? | Appointment | Yes, already booked · No, need to book now · Not sure — let me check my email · Not required for this visa type |
Capture the appointment details (free-text follow-up): date, time, visa centre / consulate, address, reference number. Read any confirmation PDF the user drops in. Save into the application folder and into the profile's current_application.appointment block.
These details are used in Phase 6 (Cover Letter mentions the appointment date) and Phase 7 (the Checklist PDF prints them as the cover sheet).
Walk the user through booking, end-to-end, via the browser MCP. Generic flow:
{destination} from {origin} (see references/known-portals.md).Country-specific nuances (see references/known-portals.md for full list):
ustraveldocs.com. Three sequential steps, not one — the skill walks the user through each.Help the user check. Common places appointments hide:
If found, treat as Branch A. If not found, treat as Branch B.
Some categories don't need an in-person appointment (Australia visas done entirely online, some Caribbean nations, ETIAS/ETA-style authorisations). Verify against the Phase 4 research — if the research already confirmed appointment-free, proceed straight to Phase 6 without further questions.
After Phase 5 settles an appointment date, automatically check:
Pre-flight: document freshness check. Before generating anything, verify the supporting documents in the folder aren't stale relative to the submission (appointment) date:
AskUserQuestion ("Latest payslip date?" / Past 30 days · 30–60 days old · Older · Don't have one). Don't generate the document pack with stale evidence.If any check fails, surface to user before generating documents — there's no point producing a Print Pack against stale evidence.
Generated documents fall into two categories with different language rules:
User-facing documents — use the user's input language:
templates/checklist.html)templates/application-status.html)templates/questionnaire.html)templates/form-data.html)When generating these, translate all visible UI text — headings, labels, button text, instructions, checklist items — to the user's language. The template's English text is a structural reference, not the final output. If the user writes in Chinese, the checklist heading should read "签证预约清单" not "Visa Appointment — Checklist". If they write in Spanish, "Lista de verificación — Cita de visado". Detect the user's language from their messages, not from the destination country.
Application-facing documents — use the language the consulate expects:
templates/cover-letter.html)templates/employment-letter.html)These are read by visa officers. Use the language required by the specific consulate (e.g., English for the Italian consulate in London, French for the French consulate in Beijing, English for the US consulate everywhere). When in doubt, default to English — it's accepted by virtually all consulates.
Every document the skill generates is produced as both HTML and PDF:
render-pdf.sh. Opens on mobile, prints cleanly, goes into the Print Pack.The workflow for each document:
templates/*.html file. For user-facing documents, also translate all static UI text to the user's language (see "Language adaptation" above).Cover Letter.html).bash scripts/render-pdf.sh "{application-folder}/Cover Letter.html" "{application-folder}/Cover Letter.pdf"
(See scripts/render-pdf.sh for the canonical invocation. It uses Chrome headless on macOS; for Linux/Windows the script self-adjusts.)
If render-pdf.sh fails (no Chrome, no fallback renderer), keep the HTML — it's still usable — and warn the user that PDF generation failed.
Don't generate all documents silently. After each document is written, tell the user in one line:
Wrote: Cover Letter.html + .pdf
Wrote: Employment Letter.html + .pdf
Wrote: Checklist.html + .pdf
Assembling Print Pack…
Print Pack ready (7 files).
This keeps the user informed without flooding them. The full manifest and review instructions come in Phase 7.
Cover letter — addressed to "Visa Section, Consulate General of {destination}, {origin city}". One page. State purpose, dates, employment, self-funding, and reference to enclosed evidence. Avoid promising things the documents don't back. See templates/cover-letter.html.
Employment letter — if employed. Should be signed by manager or HR on company letterhead. Render a draft, give to user, they get it signed. See templates/employment-letter.html.
Filled visa application form (note: this is the full visa application form, distinct from the short booking form filled in Phase 5 — they may live on the same portal or different ones). The skill fills this itself. Asking the user to fill it by hand is a last resort, not the default. Follow the four-tier strategy in references/form-filling-strategy.md:
e-applicationvisa.esteri.it, France france-visas.gouv.fr, US DS-160 ceac.state.gov, etc.), load the Chrome browser MCP via ToolSearch and fill the portal page-by-page using profile data. The portal generates a PDF with a 2D barcode the officer scans. This is the cleanest output.pymupdf detects this via doc.is_form_pdf), fill the named fields directly. Perfect alignment guaranteed.application_form_data.html + PDF from templates/form-data.html — a styled table of field names and values. Hand the user the blank printed form plus this sheet, and they transcribe.Quality gate: every tier must end with the agent visually inspecting the rendered output. Offset, overlapping, or otherwise messy PDFs are not "done" — drop down to the next tier rather than ship a poor result.
Checklist (00 - CHECKLIST) — a one-page summary the user prints as the cover sheet of the Print Pack. See templates/checklist.html. Include: appointment time/place, before-leaving-home tick list, the numbered stack order, likely officer questions and how to answer them, and the cross-checks you've already verified.
Application status — the running state tracker. See templates/application-status.html. Updated at the end of every phase that changes state.
After documents are generated, assemble the Print Pack:
bash scripts/build-print-pack.sh "{application-folder}"
This copies (not moves — keep originals) each PDF into a Print Pack/ subfolder with a numbered, human-readable prefix in the order an officer flips through them. The Print Pack uses PDFs only; the HTML copies stay in the main folder for easy browsing.
This is the user's single summary of everything the skill has done. It must be concrete and actionable — file paths the user can click, things to check, things to print. Use this exact three-part template:
List every file in the application folder, grouped by purpose. The user should be able to open Finder / Explorer to the folder and match what they see to this list.
📂 {application-folder-path}
Documents to review (double-click the .html to preview in your browser):
├── Cover Letter.html / .pdf — review wording, sign the PDF
├── Employment Letter.html / .pdf — forward to HR for signature on letterhead
├── Checklist.html / .pdf — your appointment-day cheat sheet
├── application_status.html / .pdf — tracks what's done and what's pending
└── (any other generated documents)
Documents you provided:
├── passport-bio.pdf
├── payslip-2026-04.pdf
└── … (list each)
Ready to print:
└── Print Pack/
├── 00 - CHECKLIST.pdf
├── 01 - …
└── … (numbered in officer flip-order — print all, single-sided, A4)
✅ Compliant: <list of items already verified>
❌ Still needed before {appointment date}: <specific action items>
⚠️ Verify yourself: <items the skill can't confirm, e.g., fund balance, immigration status>
📦 Bring to appointment: <visa fee amount + currency, biometric photos, original passport>
Surface any inconsistency found — date mismatches, salary discrepancies, expired documents. Better to flag now than have the officer find it.
A short numbered list of exactly what the user still has to do, in order:
1. Open Cover Letter.html — read it, make sure dates and employer are correct
2. Forward Employment Letter.pdf to your HR — they sign on letterhead and return it
3. Print everything in Print Pack/ — single-sided, A4, keep in numbered order
4. Sign the visa form (page X) and the cover letter (bottom of page 1)
5. Paperclip 2 biometric photos to file 04
6. Bring: printed stack + original passport + payment card + phone
Tailor the list to what's actually outstanding for this application. Don't include steps that are already done.
After showing this to the user, write the same state to {application-folder}/application_status.html (and re-render its PDF) so the next invocation (Phase 0 + warm-start "Continue") can read where things stand without re-prompting the user.
The workflow doesn't end when the user walks out of the visa centre. Applications take days to weeks to decide, and the skill needs to be able to pick up the thread on a later invocation.
Trigger detection: during Phase 0, after find-existing.sh folder returns a path, read that folder's application_status.html. If the status badge reads submitted or awaiting_decision or granted or refused, Phase 1's warm-start question shifts from the standard "Continue/New/Update" to the post-submission variant below.
Warm-start question (post-submission variant):
| Question | header | Options |
|---|---|---|
| Your {destination} application is in processing — what's happened? | Outcome | Still waiting / want to check status · Approved — passport collected · Refused — need to understand next steps · Other (resubmission request, courier issue, etc.) |
Help the user check status:
application_status.html (stored at the end of Phase 5).Capture from the user (or from the granted visa sticker PDF if courier-delivered with a scan):
Update:
~/.claude/visa-profile.json → append to visa_history[] with the full granted entry. The next application can answer "previous visas in last 3 years" precisely.application_status.html (+ re-render PDF) → status granted with full visa details.Briefly remind the user of:
Capture the refusal reason from the user (or read the official refusal letter PDF if they upload it). The Schengen refusal letter is highly standardised — each reason has a code (1–9) — extract the code(s).
Assess and advise (don't promise outcomes):
Update visa_history[] with outcome: "Refused" and the reason code. Most future applications require disclosure of prior refusals — this entry will be picked up automatically.
If the user wants to appeal, generate an appeal letter PDF (use a variant of the cover-letter template) addressing each refusal reason point by point. The user signs and submits within the deadline.
Free text follow-up. Common cases:
Throughout the workflow, default to acting. The user has limited time and wants the application done, not narrated.
~/Documents/Visa Applications/{Country}-{Year}/ and only ask if the user previously indicated a different location.questionnaire.html (see "Questionnaire form" section above). The user fills it at their own pace in a browser with proper form fields, pre-populated values, and a progress bar. This is a hard rule, not a suggestion.But: unsure means ask. "Do, don't ask" applies to unambiguous data. When a value could map to more than one field, or when you're interpreting what a document means rather than reading what it says, you must confirm with the user before writing it to the profile or any generated document. A wrong value silently committed to the profile will propagate to every document the skill generates — cover letter, application form, checklist — and may not be caught until the officer rejects the application. One confirmation question is cheap; a rejected visa is not.
Concrete examples of when to ask:
Where you do need to involve the user:
Real applications hit edges. When they do, be honest and useful:
Every time you finish a visa application, update ~/.claude/visa-profile.json with anything new learned (new employer, new address, new prior visa, new passport). The next application — for the same person, possibly to a different country — should be substantially faster because the profile is already 80% filled.
Also update ~/.claude/visa-history.json with a summary of what was applied for (country, type, dates, outcome if known). Past prior-visa entries matter: many Schengen forms have a "previous visas in last 3 years" question, and the past entries are exactly what's needed.
| File | Purpose |
|---|---|
SKILL.md | This file — the workflow |
references/research-protocol.md | How to research and cross-validate sources |
references/document-checklist.md | Standard document lists by visa type |
references/known-portals.md | Online application portals, appointment-booking URLs/flows, country quirks |
references/profile-schema.md | The visa-profile.json schema |
references/form-filling-strategy.md | 4-tier strategy for filling application forms without bothering the user |
references/visa-scenarios.md | Less-common scenarios (renewal, family, business, transit, student, refusal/appeal, multi-country) |
templates/cover-letter.html | Cover letter template, A4 single page |
templates/employment-letter.html | Employment letter template, A4 single page |
templates/checklist.html | Print Pack cover-sheet checklist |
templates/application-status.html | Application status tracker template |
templates/form-data.html | Tier 4 form-data sheet template (manual-transcription fallback) |
templates/questionnaire.html | Interactive HTML form for bulk data collection (replaces text-wall Q&A) |
scripts/render-pdf.sh | HTML → PDF via Chrome headless |
scripts/find-existing.sh | Search the user's machine for existing profile / folder |
scripts/build-print-pack.sh | Assemble the numbered Print Pack |
tests/smoke-test.sh | Structural sanity check (file presence, executable scripts, no leaked personal data, render pipeline) |
tests/scenarios.md | Eleven end-to-end behavioural scenarios with expected agent responses |
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub shadowhusky/visa-application --plugin visa-application