Job searching often involves repeating the same steps again and again.
Open LinkedIn. Search for roles. Filter by location. Check whether the job supports Easy Apply. Fill in the same contact details. Upload the same CV. Answer similar questions. Track which jobs were already applied to.
As a developer, I wanted to explore whether this repetitive workflow could be improved using browser automation — not as a spam tool, but as a controlled, human-supervised productivity assistant.
That led me to build LinkedIn Easy Apply Assistant, a Python-based automation project that uses Selenium to help with LinkedIn Easy Apply workflows.
The project is open source on GitHub:
https://github.com/Arul1998/linkedin-easy-apply
The Problem
Applying for jobs online can become repetitive very quickly.
Many application forms ask for the same basic information:
- First name
- Last name
- Phone number
- City
- CV upload
- Work authorization
- Notice period
- Years of experience
- Salary expectation
When someone is actively searching for jobs, they may fill the same information many times across different listings.
The goal of this project was simple:
Can I build a small automation assistant that reduces repetitive form filling while keeping the user in control?
What the Project Does
The project is a Python CLI tool that opens Chrome, logs into LinkedIn, searches for Easy Apply jobs, fills simple application forms, uploads a CV when required, and records successful applications.
At a high level, the workflow is:
- Read user configuration from
config.json - Read LinkedIn login credentials from
.env - Open Chrome using Selenium
- Log in to LinkedIn
- Search for jobs using configured filters
- Find Easy Apply jobs
- Open each job application modal
- Fill known fields from saved answers
- Upload the configured CV
- Answer simple questions using saved answers and resume-derived information
- Submit the application only when the form is manageable
- Save the application record to avoid duplicates
The assistant also includes a --dry-run mode so the user can test login and search without submitting any applications.
Important Note About Responsible Use
This project is intended as a personal productivity and learning project.
It is not designed for spam applying, bypassing platform protections, or violating website rules. Browser automation should be used carefully and responsibly.
For that reason, I added safeguards such as:
- Dry-run mode
- Confirmation mode
- Rate limiting between actions
- Rate limiting between applications
- Duplicate tracking
- Manual CAPTCHA / 2FA handling
- Skipping complex or unknown forms
- Configuration validation before running
The goal is not to apply to hundreds of jobs blindly. The goal is to reduce repetitive work while keeping the process controlled and human-supervised.
Tech Stack
The project uses:
- Python
- Selenium
- Chrome WebDriver
python-dotenvwebdriver-managerpypdf- JSON / CSV tracking
The main project files are:
main.py # CLI entry point
config.py # Loads and validates configuration
linkedin_automation.py # Selenium browser automation
resume_profile.py # Extracts resume information
tracker.py # Tracks applied jobs
session_store.py # Stores/reuses LinkedIn session cookies
errors.py # User-friendly error handling
Project Architecture
The project is split into small modules so that each file has a clear responsibility.
main.py
This is the entry point of the application.
It handles:
- CLI arguments
- Config loading
- Validation
- Browser startup
- Login flow
- Job search navigation
- Application loop
- Run summary
Some useful commands are:
python main.py --validate-only
This checks the setup without opening the browser.
python main.py --dry-run
This logs in and searches jobs but does not submit applications.
python main.py --confirm --pause-on-challenge --max-applications 5
This runs the assistant with user confirmation, CAPTCHA/2FA support, and a maximum application limit.
Configuration Design
The project separates secrets from normal configuration.
LinkedIn credentials are stored in .env:
LINKEDIN_EMAIL=your-email@example.com
LINKEDIN_PASSWORD=your-password
The job search settings and personal answers are stored in config.json:
{
"search": {
"keywords": "software engineer",
"location": "United Kingdom",
"work_type": "2",
"job_type": "F",
"date_posted": "r604800",
"experience_level": "3,4"
},
"max_applications": 5,
"resume_path": "C:/path/to/resume.pdf",
"tracking": {
"output_file": "applications.json",
"format": "json"
},
"saved_answers": {
"first_name": "Arul",
"last_name": "Cornelious",
"email": "your-email@example.com",
"phone": "your-phone-number",
"city": "St Albans",
"salary": "Negotiable",
"sponsorship": "Yes",
"start_date": "Immediately"
},
"custom_answers": {
"years of experience with angular": "3",
"are you willing to relocate": "Yes"
}
}
This design keeps sensitive credentials out of the main configuration file.
Building the LinkedIn Job Search URL
Instead of manually clicking filters, the assistant builds a LinkedIn job search URL using query parameters.
For example, it can include:
- Keywords
- Location
- Easy Apply filter
- Work type
- Job type
- Date posted
- Experience level
- Few applicants filter
- LinkedIn geo ID
The Easy Apply filter is applied through the URL so the assistant focuses only on jobs that support LinkedIn’s Easy Apply workflow.
This makes the search flow simpler and more predictable.
Selenium Automation
The browser automation is handled with Selenium.
The assistant opens Chrome, logs into LinkedIn, searches jobs, and interacts with the Easy Apply modal.
One challenge with browser automation is that websites often change their HTML structure. To make the project more stable, I used multiple CSS selectors and XPath fallbacks for important elements like:
- Job cards
- Easy Apply buttons
- Modal buttons
- Submit buttons
- Next buttons
- Review buttons
For example, the assistant does not rely on only one selector for the Easy Apply button. It checks multiple possible selectors and also uses text-based fallback logic.
This makes the automation more resilient when LinkedIn changes small parts of the UI.
Login and Session Reuse
Logging in every time can trigger extra verification.
To reduce that, the assistant stores session cookies after a successful login and reuses them in later runs.
The login flow supports:
- Normal email/password login
- Saved session reuse
- Fresh login mode
- CAPTCHA / 2FA pause mode
If LinkedIn asks for verification, the assistant can pause and allow the user to complete the challenge manually in the browser.
Example:
python main.py --pause-on-challenge
This keeps the process human-supervised instead of trying to bypass security checks.
Resume-Based Question Answering
One of the most interesting parts of the project is the resume-based question answering system.
The assistant can read the configured CV and extract useful information such as:
- Skills
- Total years of experience
- Skill-specific experience
- Work authorization text
- Notice period
- Education level
- Phone number
It supports PDF extraction using pypdf.
The answer priority is:
custom_answers → resume-derived profile → saved_answers
This means manually configured answers always win.
For example, if the application asks:
How many years of experience do you have with Angular?
The assistant checks:
- Is there a matching custom answer?
- Is Angular found in the resume?
- Can it estimate experience from the resume?
- If not, should the question be skipped?
This prevents the assistant from guessing too aggressively.
Handling Unknown Questions
Not every application form is simple.
Some forms include custom questions, long text answers, dropdowns, multi-step flows, or questions that require human judgement.
The assistant is designed to skip forms it cannot confidently complete.
If it finds a question it cannot answer, the user can add it later to custom_answers.
Example:
{
"custom_answers": {
"do you require visa sponsorship": "Yes",
"what is your expected salary": "Negotiable",
"are you willing to work hybrid": "Yes"
}
}
This makes the system improve over time while still keeping the user in control.
Tracking Applications
The assistant records every successful application in a tracking file.
Example JSON output:
[
{
"job_title": "Software Engineer",
"company_name": "Example Company",
"job_url": "https://www.linkedin.com/jobs/view/123456789/",
"date_applied": "2026-07-03 12:00:00",
"status": "applied"
}
]
This solves two problems:
- The user can review application history.
- The assistant can avoid applying to the same job twice.
The project supports both JSON and CSV tracking.
Rate Limiting
Rate limiting is important in browser automation.
The project includes two types of delay:
delay_between_actions_sec
delay_between_applications_sec
The first delay controls normal browser actions like clicks and page loads.
The second delay controls how long the assistant waits after submitting an application.
This helps keep the automation slower, safer, and more human-like.
CLI Flags
I added several CLI flags to make the tool safer and easier to test.
--dry-run
Logs in and searches jobs but does not apply.
--confirm
Shows a confirmation prompt before live application submission.
--max-applications 5
Limits how many applications can be submitted in one run.
--pause-on-challenge
Pauses when CAPTCHA or 2FA appears.
--fresh-login
Ignores saved session cookies and logs in again.
--validate-only
Checks .env and config.json without opening the browser.
These flags are useful because browser automation should be tested carefully before any real action is performed.
Challenges I Faced
1. LinkedIn UI changes
LinkedIn’s DOM can change, which means selectors can break.
To handle this, I used multiple selector strategies and fallbacks.
2. Easy Apply forms are not always the same
Some applications are one step. Some are multiple steps. Some ask custom questions. Some require dropdowns, radio buttons, or file uploads.
The assistant handles simple and predictable forms, but skips complex ones.
3. Avoiding duplicate applications
Raw LinkedIn job URLs can include tracking parameters, so the same job can appear with different URLs.
To fix this, I normalized job URLs into a cleaner format before tracking them.
4. Not over-automating
The project needed a balance between automation and responsibility.
That is why I added dry-run mode, confirmation mode, manual challenge handling, delays, and skipping logic.
What I Learned
This project helped me understand several practical engineering concepts:
- Browser automation with Selenium
- CLI design in Python
- Configuration management
- Environment variable handling
- Resume parsing
- Form-filling logic
- URL normalization
- Error handling
- Rate limiting
- Session cookie reuse
- Designing safer automation workflows
It also reminded me that automation is not just about making things faster. Good automation should also be controlled, explainable, and respectful of user intent.
Future Improvements
Some improvements I would like to add next:
- Better dashboard for application history
- Export reports by date, company, and role
- Better support for dropdowns and radio buttons
- More detailed skipped-job reasons
- Safer preview mode before submitting each application
- Local encrypted credential storage
- Unit tests for resume parsing and answer matching
- GitHub Actions workflow for linting and tests
- Optional manual review step before final submit
Final Thoughts
This project started as a simple idea: reduce repetitive job application steps.
But it became a useful engineering exercise in browser automation, form intelligence, configuration design, safety controls, and responsible automation.
The biggest lesson I learned is that automation should not remove human judgement. It should support it.
For job applications, that means helping with repetitive form filling while still allowing the applicant to choose the right roles, review their details, and stay in control.
GitHub repository:
https://github.com/Arul1998/linkedin-easy-apply
Thanks for reading. I’m open to feedback, suggestions, and ideas for making this project safer and more useful.
Top comments (1)
TextStow could be useful for this workflow — clipboard history + reusable favorites + prompt templates + cleanup for JSON/PDF/URLs. Local-first, free: textstow.com