Automate job applications on LinkedIn using Playwright MCP tools. Features Easy Apply support, target-based stopping, keyboard controls (P/R/Q), on-page status indicator, and proven automation patterns.
npx claudepluginhub yennanliu/linkedin-skill --plugin linkedin-job-auto-applyThis skill uses the workspace's default tool permissions.
This skill enables automated job applications on LinkedIn using Playwright MCP browser automation tools with intelligent target-based execution and real-time keyboard controls.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
This skill enables automated job applications on LinkedIn using Playwright MCP browser automation tools with intelligent target-based execution and real-time keyboard controls.
This skill is backed by three specialist agents. Invoke them for deeper help:
| Agent | File | When to Use |
|---|---|---|
| Strategy Agent | skills/agents/strategy-agent/SKILL.md | Filter jobs by relevance, blocklist, seniority, session budget |
| Automation Agent | skills/agents/automation-agent/SKILL.md | Timing, retry logic, rate limiting, anti-detection |
| Web Structure Agent | skills/agents/web-structure-agent/SKILL.md | Broken selectors, LinkedIn DOM changes, lazy loading |
| QA Agent | skills/agents/qa-agent/SKILL.md | Verify submissions, validate results, session reports |
1. QA Agent → preFlightCheck(page) # must PASS — abort if it fails
2. Strategy Agent → filterJobs(jobs, prefs) # score & filter before applying
3. [run automation with filtered job list]
└─ per-job: QA Agent → verifyApplicationSuccess()
4. QA Agent → generateReport() # end-of-session PASS/WARN/FAIL
Escalation during run:
Set personal values to avoid detectable generic placeholders:
const userProfile = {
phone: '+1-555-000-0000', // your real phone
linkedinUrl: 'https://www.linkedin.com/in/yourhandle',
city: 'San Francisco',
zip: '94105',
yearsExp: 5
};
await autoApplyLinkedInJobs(page, {
targetApplications: 20,
searchKeywords: 'software engineer',
userProfile // pass here
});
Use this skill when:
This skill includes ready-to-use JavaScript files:
autoApplyLinkedInJobs.js - Main automation with target-based execution and keyboard controlsapplySingleJob.js - Helper functions for single job application and listingREADME.md - Script documentation and usage guideSimply copy/paste the functions into your Playwright MCP code block to use them.
Before using this skill, ensure:
claude
# Add to marketplace
/plugin marketplace add jerryliu/linkedin-skill
# Install the skill
/plugin install linkedin-job-auto-apply
# Use the skill
/linkedin-job-auto-apply
For testing local modifications:
# Add local marketplace
/plugin marketplace add /path/to/linkedin-skill
# Install from local source
/plugin install linkedin-job-auto-apply@local
Use this for testing or applying to one job at a time. Perfect for verifying the automation works before running batch jobs.
Implementation:
Helper functions are available in applySingleJob.js.
Quick Usage:
// Copy/paste the functions from applySingleJob.js, then:
// Step 1: Navigate to search page
await page.goto('https://www.linkedin.com/jobs/search/?keywords=software%20engineer&location=United%20States&f_AL=true');
await page.waitForTimeout(3000);
// Step 2: List available Easy Apply jobs
const jobs = await listJobs(page);
console.log('Available Easy Apply jobs:', jobs);
// Output: [{ index: 0, title: "Software Engineer", company: "...", hasEasyApply: true }, ...]
// Step 3: Apply to first job (index 0)
const result = await applySingleJob(page, 0);
console.log(result);
// Output: { status: 'success', job: {...}, finalUrl: '...' }
// Step 4: Apply to another job (index 2)
const result2 = await applySingleJob(page, 2);
Returns:
{
status: 'success' | 'failed' | 'skipped' | 'error',
job: {
title: "Software Engineer",
company: "Tech Company",
location: "San Francisco, CA",
hasEasyApply: true
},
finalUrl: "https://www.linkedin.com/jobs/..."
}
💡 Tip: Always test with Mode 1 (single job) before running batch automation to verify the flow works correctly.
Use this for applying to jobs with real-time keyboard controls and target-based stopping.
Features:
Configuration:
const options = {
startPage: 1, // Starting page number
targetApplications: 20, // Target number of successful applications
maxPages: 20, // Maximum number of pages to search
searchKeywords: 'software engineer', // Job search keywords
location: 'United States', // Job location
easyApplyOnly: true // Only apply to Easy Apply jobs
};
Implementation:
The complete automation function is available in autoApplyLinkedInJobs.js.
Quick Usage:
// Copy/paste the function from autoApplyLinkedInJobs.js, then run:
// Basic: Apply to 20 Easy Apply jobs
await autoApplyLinkedInJobs(page, { targetApplications: 20 });
// Advanced: Custom configuration
await autoApplyLinkedInJobs(page, {
startPage: 1, // Start from page 1
targetApplications: 50, // Target 50 applications
maxPages: 30, // Search up to 30 pages
searchKeywords: 'backend developer',
location: 'San Francisco Bay Area',
easyApplyOnly: true
});
// While running, use keyboard controls:
// - Press P to pause
// - Press R to resume
// - Press Q to quit gracefully
Returns:
{
successful: 15, // Number of successful applications
failed: 2, // Number of failed applications
skipped: 10, // Number of skipped (already applied)
pages: [ // Per-page details
{ pageNumber: 1, successful: 8, failed: 1, skipped: 5 },
{ pageNumber: 2, successful: 7, failed: 1, skipped: 5 }
]
}
💡 Tip: For full implementation details, see
autoApplyLinkedInJobs.js. The script includes comprehensive error handling, keyboard controls, and status indicator setup.
| Option | Type | Default | Description |
|---|---|---|---|
startPage | number | 1 | Starting page number for job search |
targetApplications | number | 20 | Target number of successful applications (stops when reached) |
maxPages | number | 20 | Maximum number of pages to search |
searchKeywords | string | 'software engineer' | Job search keywords |
location | string | 'United States' | Job location |
easyApplyOnly | boolean | true | Only apply to Easy Apply jobs |
delayMin | number | 2000 | Minimum delay between applications (ms) |
delayMax | number | 4000 | Maximum delay between applications (ms) |
// Easy Apply button
'button[aria-label*="Easy Apply"]'
'button:has-text("Easy Apply")'
// Job cards
'.job-card-container'
'.jobs-search-results__list-item'
// Already applied indicator
'text=Applied'
'.job-card-container__footer-item'
// Submit button in Easy Apply modal
'button[aria-label="Submit application"]'
'button:has-text("Submit")'
// Next button in multi-step Easy Apply
'button[aria-label="Continue to next step"]'
'button:has-text("Next")'
// After navigation
await page.waitForTimeout(3000); // Let page and jobs load
// After clicking Easy Apply
await page.waitForTimeout(2000); // Let modal open
// After each form step
await page.waitForTimeout(1500); // Let next step load
// After submit
await page.waitForTimeout(2500); // Let confirmation show
// Between jobs (human-like)
const delay = 2000 + Math.random() * 2000; // 2-4 seconds random
// ✅ RELIABLE - Check for confirmation message
const successIndicator = await page.locator('text=Application sent').isVisible();
if (successIndicator) {
// Confirmed success
}
// ✅ RELIABLE - Check modal is closed
const modalClosed = await page.locator('[role="dialog"]').count() === 0;
Solution: Verify user is logged in and ensure you're filtering for Easy Apply jobs only
Solutions:
Solutions:
Solution: LinkedIn may have changed the indicator text or structure - verify the selector
Important: This tool is for educational and personal productivity purposes only.
Apply to 20 Easy Apply jobs starting from page 1, with keyboard controls available.
// Most common use case - apply to 20 jobs
await autoApplyLinkedInJobs(page, {
targetApplications: 20,
searchKeywords: 'software engineer',
location: 'United States'
});
// While running:
// - Press P to pause (take a break)
// - Press R to resume
// - Press Q to quit gracefully
Target specific job types and locations.
// Backend developer positions in Bay Area
await autoApplyLinkedInJobs(page, {
targetApplications: 30,
maxPages: 20,
searchKeywords: 'backend developer python',
location: 'San Francisco Bay Area',
easyApplyOnly: true
});
If you've already processed pages 1-5, start from page 6.
// Continue from page 6
await autoApplyLinkedInJobs(page, {
startPage: 6,
targetApplications: 20,
maxPages: 15
});
Use longer delays to be more cautious.
// Slower, more conservative automation
await autoApplyLinkedInJobs(page, {
targetApplications: 15,
delayMin: 4000, // 4 seconds minimum
delayMax: 7000 // 7 seconds maximum
});
Test the automation with Mode 1 (single job) first before batch processing.
// Navigate to search page
await page.goto('https://www.linkedin.com/jobs/search/?keywords=software%20engineer&f_AL=true');
await page.waitForTimeout(3000);
// List available jobs
const jobs = await listJobs(page);
console.log(`Found ${jobs.length} Easy Apply jobs`);
// Apply to first job (index 0) using Mode 1
const result = await applySingleJob(page, 0);
console.log('Test result:', result);
// If successful, run batch automation
if (result.status === 'success') {
await autoApplyLinkedInJobs(page, { targetApplications: 20 });
}
This skill is specifically designed for LinkedIn's unique application system:
Easy Apply Focus ⭐
Modal Management ⭐
Smart Job Filtering ⭐
LinkedIn-Specific Selectors ⭐
Keyboard Controls ⭐
Target-Based Execution ⭐
Remember: Use this skill responsibly for genuine job applications to suitable positions. Quality matters more than quantity. Be aware of LinkedIn's terms of service and use automation sparingly to avoid account restrictions.