Automate LinkedIn Easy Apply searches and applications with Puppeteer/Chromium, a verified resume PDF, remote/job-title filtering, stateful daily reruns, and...
---
name: linkedin-easy-apply
description: Automate LinkedIn Easy Apply searches and applications with Puppeteer/Chromium, a verified resume PDF, remote/job-title filtering, stateful daily reruns, and conservative answer guardrails.
when_to_use: Use when you need an AI agent to search LinkedIn jobs and submit LinkedIn Easy Apply applications from a verified resume PDF.
license: MIT
metadata:
supported_agents:
- claude-code
- openclaw
- hermes
- codex
- cursor
- windsurf
- goose
- aider
- roo-code
- cline
tags: [jobs, linkedin, automation, puppeteer, easy-apply]
---
# LinkedIn Easy Apply Automation
This skill helps an AI coding/operations agent build and run a repeatable LinkedIn Easy Apply workflow.
It is intentionally public and credential-free. It contains no usernames, passwords, cookies, private profile paths, or user-specific secrets.
## What it does
- Searches LinkedIn Jobs with Easy Apply enabled.
- Supports remote-only and location-constrained searches.
- Focuses on configurable role keywords such as AI, LLM, Claude, OpenAI, Codex, full-stack, frontend, backend, web, software, JavaScript, TypeScript, Node, React, and Svelte.
- Opens the LinkedIn Easy Apply flow reliably with a direct apply URL when possible.
- Uploads a verified resume PDF.
- Answers only verified applicant facts.
- Skips unknown required questions, compensation questions, custom essays, or subjective fields.
- Keeps state and JSONL logs so daily reruns avoid duplicates.
## Safety rules
- Do not store LinkedIn credentials in scripts, skills, memory, logs, or reports.
- Prefer a persistent browser profile where the operator logs in manually once.
- Stop for CAPTCHA, MFA, suspicious-login checks, identity verification, or account-security prompts.
- Do not fabricate applicant facts.
- Do not answer custom freeform questions or compensation expectations without operator-provided answers.
- Re-check full job descriptions for hybrid/location constraints before submitting remote-only applications.
## Environment variables
```bash
RESUME_PDF=/absolute/path/to/resume.pdf
CHROME_PROFILE=$HOME/.cache/linkedin-chrome
STATE_DIR=/tmp/linkedin-easyapply-daily
MAX_SCAN=80
MAX_APPLY=10
DRY_RUN=1
SEARCHES='Claude|OpenAI|Codex|LLM engineer|AI engineer|full stack engineer|software engineer'
LOCATION='United States'
REMOTE_ONLY=1
```
## Puppeteer launch pattern
```js
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
headless: false,
executablePath: process.env.CHROME_BIN || '/snap/bin/chromium',
userDataDir: process.env.CHROME_PROFILE || `${process.env.HOME}/.cache/linkedin-chrome`,
defaultViewport: null,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--start-maximized']
});
const page = await browser.newPage();
page.setDefaultTimeout(30000);
page.on('dialog', async d => {
try {
if (d.type() === 'beforeunload') await d.accept();
else await d.dismiss();
} catch {}
});
```
## Search workflow
Build LinkedIn Jobs URLs using explicit filters:
- `f_AL=true` for Easy Apply.
- Remote filter when remote-only is required.
- Location such as `United States` when requested.
- Configurable keywords matching the resume and target market.
Keep candidates only when the card/page supports the requested constraints and the title/description matches the target role family.
## Direct Easy Apply URL
When a LinkedIn job ID is known, try the direct flow first:
```js
const applyUrl = `https://www.linkedin.com/jobs/view/${jobId}/apply/?openSDUIApplyFlow=true`;
await page.goto(applyUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });
```
If the modal does not open, fall back to clicking visible `Easy Apply` controls across buttons and links.
## Conservative answer rules
Answer only facts that are verified by the resume, profile, or explicit operator instruction:
- Work authorization / eligible to work: yes only if verified.
- Visa sponsorship required: no only if verified.
- Remote willingness: yes only for remote-only searches.
- Years of experience: use resume-backed values or a user-approved mapping.
- Email/phone/location: use existing profile/resume facts.
- Middle name: blank if none provided.
Skip:
- Salary/compensation expectations.
- Cover-letter text prompts.
- Custom essays.
- Relocation/hybrid/travel if not approved.
- Unclear required inputs, radios, or selects.
## Multi-step apply loop
1. Open the apply flow.
2. Detect already-submitted applications.
3. Upload/select the verified resume PDF if needed.
4. Inspect each step's visible inputs/selects/radios.
5. Fill only safe verified answers.
6. Click `Next`, `Review`, then `Submit application` only when no unknown required fields remain.
7. Log submitted/skipped state.
## Suggested state
```json
{
"seen": {},
"applied": {},
"skipped": {},
"alreadySubmitted": {}
}
```
Write state and logs to:
```text
/tmp/linkedin-easyapply-daily/state.json
/tmp/linkedin-easyapply-daily/results.jsonl
```
## Reporting
Report concise results:
```text
Submitted:
- Title — Company — URL
Skipped:
- Title — Company — URL — reason
State: /tmp/linkedin-easyapply-daily/state.json
Log: /tmp/linkedin-easyapply-daily/results.jsonl
```
## Pitfalls
- Built-in browser tools often time out on LinkedIn. Puppeteer with Chromium and Xvfb is usually more reliable.
- LinkedIn search cards can say Remote while the description includes hybrid/local constraints. Inspect the full page before applying.
- Broad keyword matching can create bad answers. Avoid treating generic words like `United States` or `comfortable` as approval.
- Direct DOM value assignment may not update React forms. Prefer click/type and dispatch input/change events.
- If a page detaches or a modal closes unexpectedly, create a new page and continue from the state file.
don't have the plugin yet? install it then click "run inline in claude" again.