LinkedIn-Easy-Apply-Bot drives the browser with Selenium and fills applications from a YAML file
Automate the application process on LinkedIn
At a glance
- What is it?
- LinkedIn-Easy-Apply-Bot is a Python script that automates the Easy Apply flow on LinkedIn, configured from a single config.yaml holding your credentials, the job titles and locations to search, the files to upload and a company blacklist. It runs on Selenium with pyautogui for keyboard and mouse input, it writes results to a CSV, and the README is sponsored by a commercial product that does the same job as a browser extension.
- Who is it for?
- This repository fits someone who wants to understand or modify a Selenium based job application flow, or who wants an unattended script on a Linux box they control. It is the wrong tool if you want a supported product, since the README's own sponsor sells a commercial extension for the same job, and the script carries real exposure for the account it runs under.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 16 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the script actually does
The repository describes itself in one line, automate the application process on LinkedIn, and the implementation is a single Python entry point, easyapplybot.py, at the root of the repository.
The approach is browser automation rather than an API, and the dependency list makes the technique clear. Selenium drives the browser, BeautifulSoup at version 4.9.1 parses the pages it lands on, pyautogui at 0.9.50 sends keyboard and mouse events, lxml is there as the parser backend, pandas at 1.3.1 handles the tabular results, and PyYAML at 5.3.1 reads the configuration. There is a webdriver_manager directory in the repository, which is the standard way of fetching a matching browser driver, and python-dotenv and future are also in the requirements.
The scale the author writes about is in the write-up linked from the README, whose title is How to apply for 1,000 jobs while sleeping, and there is also a video walkthrough. That framing tells you the intent, and it is worth being clear about what it means in practice. This is a script that repeatedly drives a logged-in browser through an application flow, filling forms, uploading files and moving on to the next posting, without a person in the loop for each one.
That is a materially different thing from a job alert or an application tracker. The work is done by your account, on your account's session, with whatever reputation that session carries.
config.yaml holds your password, so do not commit it
All the configuration lives in one file, config.yaml, which sits in the repository root next to easyapplybot.py. The structure covers the account details, the search, the uploads, the output and the blacklist.
username: # Insert your username here
password: # Insert your password here
phone_number: #Insert your phone number
positions:
- # positions you want to search for
- # Another position you want to search for
- # A third position you want to search for
locations:
- # Location you want to search for
- # A second location you want to search in
salary: #yearly salary requirement
rate: #hourly rate requirement The README states the rule plainly in capitals: after editing, save the file, do not commit the file. That instruction exists because the file contains a plaintext password and a plaintext phone number, and because config.yaml is a tracked file in the repository. A git history is a poor place to keep either, and a shared CI log or a fork is a worse one.
The rest of the file continues the same pattern. The uploads section maps labels to paths, the output_filename list holds the path to the output file with a default of output.csv, and the blacklist is a list of company names to ignore, which is a way to keep the script away from employers you do not want to apply to.
The salary and rate fields are filters rather than settings you send, with the yearly and the hourly requirement as separate keys, which is a detail worth checking against how the search form on the site presents them.
Uploads are matched by the title on the input box
The uploads section is the part of the configuration with real logic behind it, and the README gives it its own section.
The file names are the keys of a mapping, and they are matched against what the form asks for.
uploads:
Resume: # PATH TO Resume
Cover Letter: # PATH TO cover letter
Photo: # PATH TO photo
# Note file_key:file_paths contained inside the uploads section should be written without a dash ('-') There is no limit to the number of files you can list in the uploads section. The program takes the titles from the input boxes and tries to match them with the list in the config file.
That describes a two way matching rather than a fixed position. The script reads the label of the file input on the page, looks for a config key with the same title, and uploads the file at the path you gave it. The consequence is that a mismatch is silent: if the site renames a field, or if an application form asks for a document your config does not name, the script finds no key and the upload does not happen.
The note about the dash is a warning from experience. The keys and paths are written without a dash, and the format of the entries is a key and a path, separated by a colon. YAML will happily read other things, so a stray dash turns a mapping into a list and the matching quietly stops working.
Two ways of driving the page, in one script
The dependency list contains two different automation techniques, and the combination is more informative than either alone.
BeautifulSoup with lxml is DOM level work. The script fetches a page, parses it, finds elements and reads their text, which is how you locate a job posting, read a button label, or decide whether a posting is one you want to answer.
pyautogui is OS level work. It moves the real mouse and sends real key events to the real focused window, which is how you get past the parts of a site that are not in the DOM, or that only respond to a trusted input event.
Combining them is a common pattern in Selenium scripts and it has a specific consequence. The DOM half breaks when the site changes its markup, and the pyautogui half breaks when the site changes the position of a control or the sequence of screens. Both are outside the repository's control, and neither failure announces itself in a way that is easy to attribute, because a mis-click in the wrong place can also look like a parsing failure.
The environment this implies is worth stating. The setup section says Python 3.10 using a conda virtual environment on Linux, Ubuntu. A script that drives the mouse and the keyboard needs a graphical session and a real display, which rules out a plain headless server without a virtual display, and it rules out running it while you use the same machine.
Running it takes three commands
The setup is short, and the README is explicit about the environment it was written for.
pip3 install -r requirements.txtThat installs the dependency list into the environment, and the README places it in a conda virtual environment on Ubuntu with Python 3.10. The repository also carries a shell.nix file, so a Nix environment is possible for anyone who prefers one.
The script itself is started with the entry point at the root of the repository.
python3 easyapplybot.pyThere is no command line interface, no flag for a dry run, and no configuration of the run beyond the file. That is worth noticing, because it means the first execution of the script on your account is also the first execution of it on your account, and the output lands in the CSV file named in output_filename, with the default being output.csv. The repository contains out.csv and qa.csv as examples of what the output looks like.
The project is licensed under Apache-2.0, with the licence file at the repository root, the last push was on 2026-10-07, and the repository publishes no GitHub releases, so there is no version to pin and you are running the current state of master.
The sponsored product does the same job as an extension
The most commercially interesting thing in the README is the banner at the top, and it is worth reading as a comparison rather than as an advertisement.
The sponsor is Zapply, and the README describes a Chrome extension that autofills job applications in seconds, a dedicated job board with the latest openings across various roles, user accounts with multiple profiles for different resume types and roles, and job application tracking with streaks and commitment awards. The banner is marked as sponsored.
The difference in approach is the interesting part, and it is a real one. The script drives the whole browser, owns the login, parses the search results, opens each posting, fills each form, uploads each document and records the result. The extension sits inside the browser you are already logged into, and autofills the form once you have found a job you want. One is an unattended pipeline, the other is an assistant.
That difference has consequences for cost and for risk. A pipeline needs a machine, a session and a configuration, and it works while you sleep. An assistant needs a person and a browser tab, and it never touches the account without you. The README is honest that the sponsored path exists, which is more useful to a reader than a repository that pretended there was no alternative.
What the README does not address
Several things a reader would want to know are simply not in the documentation, and their absence is itself information.
The README does not discuss the terms of service of the site it automates. Automating an application flow at the scale described in the write-up is the kind of behaviour a platform's rules are written about, and the repository says nothing about it. It also does not describe what happens to the session when the site challenges it, whether that is a captcha, a rate limit, or a request for verification, because there is no section on failures at all.
There is no logging beyond the CSV output, no retry policy, and no way to resume a run that stopped halfway. The uploader matching described earlier fails quietly when a field name changes, and a script that runs unattended for hours will not tell you which application it filled and which one it skipped.
The credential handling is the other gap. The password goes into config.yaml in plain text, the file is tracked in the repository, and the instruction to not commit it is a warning in a README rather than a mechanism. A .gitignore entry, an environment variable, or a prompt at startup would be a small change, and none of them is there.
None of that makes the script wrong for the job the author built it for, but it does define the audience. It is a script to run, look at and modify on a machine you control, not a component to deploy in a service where an account and a password are long lived secrets.
Editorial conclusion
This repository fits someone who wants to understand or modify a Selenium based job application flow, or who wants an unattended script on a Linux box they control. It is the wrong tool if you want a supported product, since the README's own sponsor sells a commercial extension for the same job, and the script carries real exposure for the account it runs under. Verify first that you can run a graphical session on the machine, because pyautogui drives the real keyboard and mouse, and keep config.yaml out of version control, since it holds your password in plain text.
Frequently asked questions
How do I run the LinkedIn Easy Apply Bot?
The setup targets Python 3.10 in a conda virtual environment on Ubuntu, with the dependencies installed with pip3 install -r requirements.txt. You then fill in config.yaml and start the script with python3 easyapplybot.py, and the repository also ships a shell.nix file.
What goes into the config.yaml file?
Your username, password and phone number, the positions and locations to search for, the yearly salary and hourly rate requirements, the uploads mapping with the paths of your resume, cover letter and photo, the output_filename list which defaults to output.csv, and a blacklist of company names to ignore.
How are the file uploads matched to the application form?
There is no limit to the number of files in the uploads section. The program takes the titles from the input boxes and tries to match them with the list in the config file, and the README notes that the file_key and file_paths entries should be written without a dash.
Does the bot need a graphical session?
The requirements include pyautogui, which sends real keyboard and mouse events, so the script needs a desktop session rather than a bare headless server. The setup section describes Linux on Ubuntu with Python 3.10 in a conda virtual environment.
Is there a maintained alternative to the script?
The README sponsors a commercial product, Zapply, whose Chrome extension autofills job applications, with a job board, multiple profiles for different resume types, and application tracking. The difference in approach is that the script runs the whole pipeline unattended while the extension fills a form you have already opened.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/nicolomantini-linkedin-easy-apply-bot)