How to create job listing page and view job application status in Drupal

Every corporate websites need a careers page where candidates can browse jobs and apply for relevant jobs and also registered users can tracj their job application status.

Here we are going to discuss how to create a page which list jobs and apply jobs with cv/resume and how to view job application status in candidate profile page.

So here we will be creating two content types, jobs and job applications and a custom module for resume submission.

We will be achieving all these with below steps.

  1. Create job offers content type for creating jobs

  2. create a view for displaying jobs with apply link.

  3. Create a content type for Job applications.

  4. Custom form for applying job with resume for anonymous users.

  5. Custom form for applying job with resume for registered users.

  6. Job applications list in user profile page with status of the application.

Here we are discussing each steps in detail. You can download sample modules module here which has two forms for submitting job application with CV/resume document.

Step 1: create Job offers content type for creating jobs

Create content type, say jobs.

So we are providing here below fields

Title – This would be the Job title.

Body – For job description

You can add additional fields like expiry date some thing like that. Here We are providing only basic fields.

Step 2: create a view for displaying jobs with apply link

Create a basic view with job title as field.

In content ID field ,re write the result as below in order to accommodate Apply button.

So we have provided below anchor tags

<a class=”use-ajax” href=”/ajax/job/apply/{{ nid }}”> Apply</a><br>

<a href=”/registered/job/apply/{{ nid }}”> Login and Apply</a>

View results will be as below.

You can apply your own customization by overriding view templates. See below articles to see how you can override view and node templates.

How to override view templates

How to override node templates

Here we are using class=”use-ajax” in order to popup ajax form where candidates can submit resume. In Step 4 we will be creating custom for capture this apply action.

Step 3: create a content type for Job applications

In this step create a job applications content type.

Here Resume is a file upload field and Application status is a drop down list.

Th predefined values , you can add key and values are like Rejected, In progress, On hold like that.

We have added below values.

IN next steps we will see  programmatically inserting content to this content while user submits CV.

Here admin user can verify each resume and change status accordingly.

Step 4: Custom form for for applying job with resume for anonymous users

Here we are going to create two custom form for below routs.

/ajax/job/apply/{{ nid }}

/registered/job/apply/{{ nid }}

While click on apply button we will be loading below pop up form.

We have a custom module dn_careers.

We have below folder structure in module.

You can see complete source code here.

First we are below routes in dn_careers.routing.yml

dn_careers.job_apply_ajax:
  path: '/ajax/job/apply/{cid}'
  defaults:
    _controller: '\Drupal\dn_careers\Controller\JobController::applyJobAjax'
    _title: 'Apply Job'
  requirements:
    _access: 'TRUE'
    
dn_careers.job_apply_registered:
  path: '/registered/job/apply/{jobId}'
  defaults:
    _title: 'Apply Job'
    _form: '\Drupal\dn_careers\Form\JobApplyRegisteredForm'
  requirements:
   _access: 'TRUE'

In Job controller provided below applyJobAjax function.

public function applyJobAjax($cid) { 
  
	$record = array('node_id'=>$cid);
	$render_array = \Drupal::formBuilder()->getForm('Drupal\dn_careers\Form\JobApplyForm',$record);
	$response = new AjaxResponse();
	$response->addCommand(new OpenModalDialogCommand('Submit Application', $render_array, ['width' => '800']));
	 
    return $response;
  }

applyJobAjax function loads popup form . complete source code of JobController.php provided below.

\dn_careers\src\Controller\JobController.php

<?php
namespace Drupal\dn_careers\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Database;
use Drupal\Core\Url;
use Drupal\Core\Routing;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Render\Markup;

/**
 * Class JobController.
 *
 * @package Drupal\dn_careers\Controller
 */
class JobController extends ControllerBase {

/**
   * The form builder.
   *
   * @var \Drupal\Core\Form\FormBuilder
   */
  protected $formBuilder;

  /**
   * The JobController constructor.
   *
   * @param \Drupal\Core\Form\FormBuilder $formBuilder
   *   The form builder.
   */
  public function __construct(FormBuilder $formBuilder) {
    $this->formBuilder = $formBuilder;
  }
/**
   * {@inheritdoc}
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   The Drupal service container.
   *
   * @return static
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('form_builder')
    );
  }
  
 
  
   /**
   * {@inheritdoc}
   * update the given student
   */
  public function applyJobAjax($cid) { 
  
	$record = array('node_id'=>$cid);
	$render_array = \Drupal::formBuilder()->getForm('Drupal\dn_careers\Form\JobApplyForm',$record);
	$response = new AjaxResponse();
	$response->addCommand(new OpenModalDialogCommand('Submit Application', $render_array, ['width' => '800']));
	 
    return $response;
  }
}

Create a job submission form in below path for anonymous users.

\dn_careers\src\Form\JobApplyForm.php

See the below source of this file.

<?php

namespace Drupal\dn_careers\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Url;
use Drupal\Core\Routing;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Form\FormState;
use Drupal\node\Entity\Node;
use Drupal\media\Entity\Media;
/**
 * Provides the form for edit students.
 */
class JobApplyForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'dn_student_form_edit';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state,$record = NULL) {
   
	if(!empty($record['node_id'])){
	$job = Node::load($record['node_id']);
    $title = $job->getTitle();
	$description = $job->get('body')->getValue();
	$job_title = $job->get('field_job_title')->getValue()[0]['value'];
   //print_r($job_title);
	}
    if(isset($record['node_id'])){
		$form['node_id'] = [
		  '#type' => 'hidden',
		  '#attributes' => array(
             'class' => ['txt-class'],
           ),
		  '#default_value' => (isset($record['node_id'])) ? $record['node_id'] : '',
		];
	}
	
	$form['job_title'] = [
		  '#type' => 'hidden',
		  '#attributes' => array(
             'class' => ['txt-class'],
           ),
		  '#default_value' =>  $job_title,
		];
	
    $form['fname'] = [
      '#type' => 'textfield',
      '#title' => $this->t('First Name'),
	  '#prefix' => '<div class="msg"></div><a name="my_form_item">'.$job_title.'</a>', 
      '#required' => TRUE,
      '#maxlength' => 20,
	  '#attributes' => array(
       'class' => ['txt-class'],
       ),
      '#default_value' => (isset($record['fname'])) ? $record['fname'] : '',
    ];
	 $form['sname'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Second Name'),
      '#required' => TRUE,
      '#maxlength' => 20,
	  '#attributes' => array(
       'class' => ['txt-class'],
       ),
      '#default_value' => (isset($record['sname'])) ? $record['sname'] : '',
	  
    ];
	
	$form['upload']['job_resume'] = [
    '#type'                 => 'managed_file',
    '#upload_location'      => 'public://',
    '#multiple'             => TRUE,
    '#description'          => t('Allowed extensions: dcoc docx pdf'),
    '#upload_validators'    => [
      'file_validate_is_resume_empty'      => array(),
      'file_validate_extensions'    => array('pdf doc docx'),
      'file_validate_size'          => array(25600000)
    ],
    '#title'                => t('Upload your resume document')
    ];
	
	
	
    $form['actions']['#type'] = 'actions';
    $form['actions']['Save'] = [
      '#type' => 'submit',
      '#button_type' => 'primary',
	   '#attributes' => [
        'class' => [
          'use-ajax',
        ],
      ],
	  '#ajax' => ['callback' => '::saveJobData'] ,
      '#value' => (isset($record['fname'])) ? $this->t('Update') : $this->t('Save') ,
    ];
	
	 
	
	 $form['#prefix'] = '<div class="form-div-edit" id="form-div-edit">';
	 $form['#suffix'] = '</div>';
	
	  $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
    return $form;

  }
  
   /**
   * {@inheritdoc}
   */
  public function validateForm(array & $form, FormStateInterface $form_state) {
        //print_r($form_state->getValues());exit;
		
  }
  
   public function file_validate_is_resume_empty(array & $form, FormStateInterface $form_state) {
        
		
  }
 
  public function saveJobData(array $form, FormStateInterface $form_state) {
	  $response = new AjaxResponse();
	  $response->addCommand(new HtmlCommand('.msg','' ));
	 try{
			$field = $form_state->getValues();
			if( $field['node_id'] != '' && !empty($field['node_id'])){
				 $file_data = $form_state->getValue(['upload' => 'job_resume']);
				 $file = \Drupal\file\Entity\File::load( $file_data[0] );
				 $file_name = $file->getFilename();
				 $file->setPermanent();
				 $file->save();				 
				 //====load job details
				 $job = Node::load($field['node_id']);
				$title = $job->getTitle();
				$description = $job->get('body')->getValue();
				$job_title = $job->get('field_job_title')->getValue()[0]['value'];
				 //====load job details	 
				 $contentType  = 'job_application';
					$node = Node::create(['type' => $contentType]);
					//$node->langcode = "fr";
					$node->uid = 1;
					$node->promote = 0;
					$node->sticky = 0;
					$node->title=  $job_title .' - Application';
					$node->body = '';
					//$node->field_1 = $field_1;
					$node->field_resumes = ['target_id' => $file->id()];
					$node->field_first_name = $field['fname'];
					$node->field_last_name =  $field['sname'];
					$node->save();
					$nid = $node->id();
					
				 //======Job application node save
				
				$response->addCommand(new \Drupal\Core\Ajax\AppendCommand('.msg', "You have auccessfully applied for the Job - ".$job_title));
			}
		} catch(Exception $ex){
		   \Drupal::logger('dn_careers')->error($ex->getMessage());
		   
		   $response->addCommand(new \Drupal\Core\Ajax\AppendCommand('.msg', "Some thing went wrong"));
		   
	    }
		
		 return $response;
  }
 
  
  
  /**
   * {@inheritdoc}
   */
 public function submitForm(array & $form, FormStateInterface $form_state) {
	 
	 
	
  }

}
  

Here validations and other details you can include. Only first name , last name and resume fields are provided.

You can see using below code snippet, we are loading job details. You can populate all fields of the job in submission page.

if(!empty($record['node_id'])){
	$job = Node::load($record['node_id']);
    $title = $job->getTitle();
	$description = $job->get('body')->getValue();
	$job_title = $job->get('field_job_title')->getValue()[0]['value'];
	}

Also below code will dynamically create Job application node with uploaded resume/CV. After saving page provides a success message as ajax call response.

if( $field['node_id'] != '' && !empty($field['node_id'])){
				 $file_data = $form_state->getValue(['upload' => 'job_resume']);
				 $file = \Drupal\file\Entity\File::load( $file_data[0] );
				 $file_name = $file->getFilename();
				 $file->setPermanent();
				 $file->save();				 
				 //====load job details
				 $job = Node::load($field['node_id']);
				$title = $job->getTitle();
				$description = $job->get('body')->getValue();
				$job_title = $job->get('field_job_title')->getValue()[0]['value'];
				 //====load job details	 
				 $contentType  = 'job_application';
					$node = Node::create(['type' => $contentType]);
					//$node->langcode = "fr";
					$node->uid = 1;
					$node->promote = 0;
					$node->sticky = 0;
					$node->title=  $job_title .' - Application';
					$node->body = '';
					//$node->field_1 = $field_1;
					$node->field_resumes = ['target_id' => $file->id()];
					$node->field_first_name = $field['fname'];
					$node->field_last_name =  $field['sname'];
					$node->save();
					$nid = $node->id();
					
				 //======Job application node save
				
				$response->addCommand(new \Drupal\Core\Ajax\AppendCommand('.msg', "You have successfully applied for the Job - ".$job_title));

Similar way you can create a form for registered users as below.

\dn_careers\src\Form\JobApplyRegisteredForm.php

<?php

namespace Drupal\dn_careers\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Url;
use Drupal\Core\Routing;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Form\FormState;
use Drupal\node\Entity\Node;
use Drupal\media\Entity\Media;
/**
 * Provides the form for edit students.
 */
class JobApplyRegisteredForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'dn_student_form_edit';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state,$jobId = NULL) {
    if(!empty($jobId)){
	$job = Node::load($jobId);
    $title = $job->getTitle();
	$description = $job->get('body')->getValue();
	$job_title = $job->get('field_job_title')->getValue()[0]['value'];
   //print_r($job_title);
	
	
   //print_r($job_title);
    if(isset($jobId)){
		$form['node_id'] = [
		  '#type' => 'hidden',
		  '#attributes' => array(
             'class' => ['txt-class'],
           ),
		  '#default_value' => (isset($jobId)) ? $jobId : '',
		];
	}
	
	$form['job_title'] = [
		  '#type' => 'hidden',
		  '#attributes' => array(
             'class' => ['txt-class'],
           ),
		  '#default_value' =>  $job_title,
		];
	
    $form['fname'] = [
      '#type' => 'textfield',
      '#title' => $this->t('First Name'),
	  '#prefix' => '<div class="msg"></div><a name="my_form_item">'.$job_title.'</a>', 
      '#required' => TRUE,
      '#maxlength' => 20,
	  '#attributes' => array(
       'class' => ['txt-class'],
       ),
      '#default_value' => '',
    ];
	 $form['sname'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Second Name'),
      '#required' => TRUE,
      '#maxlength' => 20,
	  '#attributes' => array(
       'class' => ['txt-class'],
       ),
      '#default_value' => '',
	  
    ];
	
	$form['upload']['job_resume'] = [
    '#type'                 => 'managed_file',
    '#upload_location'      => 'public://',
    '#multiple'             => TRUE,
    '#description'          => t('Allowed extensions: dcoc docx pdf'),
    '#upload_validators'    => [
      'file_validate_is_resume_empty'      => array(),
      'file_validate_extensions'    => array('pdf doc docx'),
      'file_validate_size'          => array(25600000)
    ],
    '#title'                => t('Upload your resume documents')
    ];
	
	
	
    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#button_type' => 'primary',
      '#default_value' => $this->t('Save') ,
    ];
	
	 
	
	 $form['#prefix'] = '<div class="form-div-edit" id="form-div-edit">';
	 $form['#suffix'] = '</div>';
	
	  
    return $form;
	}
  }
  
   /**
   * {@inheritdoc}
   */
  public function validateForm(array & $form, FormStateInterface $form_state) {
        //print_r($form_state->getValues());exit;
		
  }
  
   public function file_validate_is_resume_empty(array & $form, FormStateInterface $form_state) {
        
		
  }
 
  
  
 
  /**
   * {@inheritdoc}
   */
 public function submitForm(array & $form, FormStateInterface $form_state) {
	 
	 
	 try{
		
			$field = $form_state->getValues();
			//echo $field['node_id'] ;exit;
			if( $field['node_id'] != '' && !empty($field['node_id'])){
				 $file_data = $form_state->getValue(['upload' => 'job_resume']);
				 $file = \Drupal\file\Entity\File::load( $file_data[0] );
				 $file_name = $file->getFilename();
				 $file->setPermanent();
				 $file->save();
				 
				 //====load job details
				 $job = Node::load($field['node_id']);
				$title = $job->getTitle();
				$description = $job->get('body')->getValue();
				$job_title = $job->get('field_job_title')->getValue()[0]['value'];
				 //====load job details	 
				 $contentType  = 'job_application';
					$node = Node::create(['type' => $contentType]);
					//$node->langcode = "fr";
					$node->uid = \Drupal::currentUser()->id();
					$node->promote = 0;
					$node->sticky = 0;
					$node->title=  $job_title .' - Application';
					$node->body = '';
					//$node->field_1 = $field_1;
					$node->field_resumes = ['target_id' => $file->id()];
					$node->save();
					$nid = $node->id();
					
				
				 \Drupal::messenger()->addMessage($this->t('Job successfully submitted'));
			}
		} catch(Exception $ex){
		   \Drupal::logger('dn_careers')->error($ex->getMessage());
		  		   
		   
	    }
	 
	
  }

}
  

Also we have to create one event subscriber in order check whether user is logged or not.

for this, create a services.yml file  \dn_careers\dn_careers.services.yml

services:
  dn_careers.event_subscriber:
    class: Drupal\dn_careers\EventSubscriber\RedirectAnonymousSubscriber
    arguments: []
    tags:
      - {name: event_subscriber}

Also create subscriber class in path

\src\EventSubscriber\RedirectAnonymousSubscriber.php

<?php

namespace Drupal\dn_careers\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;


/**
 * Event subscriber subscribing to KernelEvents::REQUEST.
 */
class RedirectAnonymousSubscriber implements EventSubscriberInterface {

  public function checkAuthStatus(GetResponseEvent $event) {

    global $base_url;

   

      if(\Drupal::routeMatch()->getRouteName() == 'dn_careers.job_apply_registered' && \Drupal::currentUser()->isAnonymous()){
		  $response = new RedirectResponse($base_url . '/user/login', 301);
		  $event->setResponse($response);
		  $event->stopPropagation();
		  return;
	  }
  /*  }*/
  }

  public static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = array('checkAuthStatus');
    return $events;
  }
}

Download complete sample code of two form here.

Sep 6: Job applications list in user profile page with status of the application.

Here we will be displaying all jobs applied by users in their dashboard menu tab.

You can refer below article for placing a new tab in your user account menu.

Add Custom Tab to User Profile Page with Views in Drupal

Here we are going to create a job application listing tab in user profile page.

Here we are going to create a view My Applications

View Name – My Applications

select Display as Page

you can add required fields need to be shown in user profile page. Here I just selected application status and Job application title.

Make sure path of page is of below format.

user/%user/my-applications

As a next step add Authored by context filter.

In context filter select below options.

Further down in same settings page select below options

Check “Specify validation criteria”.
Select “User ID” from Validator.
Check “Validate user has access to the User”.
Under “Access operation to check” select Edit.

For showing this view in user profile tab, click on no menu link under page settings.

select type as menu tab and provide title. Select parent as User account menu. see below screen.

Click Apply.

Now if you go to your user profile page -> /user

you can see Job applications tab with other tabs.

So you can see job title and status here.

Download sample moduel here.

Get Free E-book
Get a free Ebook on Drupal 8 -theme tutorial
I agree to have my personal information transfered to MailChimp ( more information )
if you like this article Buy me a Coffee this will be an ispiration for me to write articles like this.

You may also like...