Payslips Portal Example (HR)

Step-by-step, screenshot-driven guide to configuring a full-featured Payslips portal—end-to-end, from setup to automated publishing and user access.


This tutorial walks you through building a secure, automated "Payslips" portal for HR, expanding on the simpler "Paystub" example. You’ll configure a richer content type, adapt templates, automate publishing from ReportBurster, and verify user access—all with screenshots and practical steps.

Table of Contents

  1. Create the Payslip Content Type in Portal Admin UI
  2. Add a Payslip Entry for Testing
  3. Update and Adapt Your Portal Scripts
  4. Adapt the Single Document Template
  5. Adapt the "My Documents" List Template
  6. Automate Publishing from ReportBurster
  7. Log In and Check Payslips as an Employee User
  8. Tips & Troubleshooting

1. Create the Payslip Content Type in Portal Admin UI

Portal admin UI showing Payslip content type and fields Caption: Define your Payslip content type and fields in the portal admin UI.

To start, define your new "Payslip" content type in the portal admin UI.

  • Go to Pods Admin in your portal dashboard.
  • Click Add New to create a new Pod.
  • Choose Custom Post Type and name it Payslip.
  • Add all the fields needed for your HR scenario.
  • Save your Pod.

2. Add a Payslip Entry for Testing

Adding a new Payslip entry in Portal Admin Caption: Add a sample Payslip entry for testing.

Before automating, add a sample Payslip entry to verify your setup.

  • Go to Payslips in the portal admin menu.
  • Click Add New.
  • Fill in the fields for a test employee and period.
  • Save the entry.

3. Update and Adapt Your Portal Scripts

To fully set up your new "Payslip" content type, you’ll update two existing scripts and create one new file:

  • Create single-payslip.php — displays individual Payslip documents to users.
  • Update page-my-documents.php — shows the list of Payslips for each user (their account page).
  • Update endExtractDocument.groovy — uploads your Payslip document data to ReportBurster2Portal.

Tip:
You don’t need to write these scripts yourself (unless you want to).
AI will generate the code for you, so you can focus on building your portal.

Hey AI, Help Me ReportBurster Portal button

4. Adapt the Single Document Template

Portal templates in file explorer Caption: Locate and copy the single document template.

Make sure each Payslip displays the way you need.

  • Copy single-paystub.php to single-payslip.php in your theme or plugin.
  • Use AI to update the template for the new fields.

ReportBurster Portal AI Prompt to generate the single template for custom type Caption: Use the AI prompt to generate your custom template.

Replace the marked regions with your Payslip field list and the exact content of single-paystub.php.
Add any layout details you want, then pass the prompt to your preferred AI chat vendor.
Paste the returned code into your new single-payslip.php file.

<?php
/**
 * Secure single payslip template for Pods "payslip" content type.
 *  
 * Plain PHP (WordPress, Pods Framework, Tailwind CSS)
 *
 * Features:
 *  - Requires login.
 *  - Ownership via Pods User Relationship field: associated_user (if present).
 *
 * OPTIONAL Pods fields you may add (all boolean/relationships can be left empty):
 *  - allow_public_view     (Boolean)            If true: anyone with URL can view (no auth required).
 *  - associated_user       (User Relationship)  Exact WP User owner.
 *  - associated_groups      (Pick / Multi-Select) One or more WP group slugs allowed (e.g. it, hr).
 *  - associated_roles       (Pick / Multi-Select) One or more WP role slugs allowed (e.g. employee, customer).
 *
 * Logic (in order):
 *  1. If allow_public_view = true => bypass all other checks.
 *  2. Otherwise user must be logged in.
 *  3. If associated_user set => only that user (or admin) allowed.
 *  4. Else if associated_groups set => user must be in at least one matching group.
 *  5. Else if associated_roles set => user must have at least one matching role.
 *  6. Else fallback: require logged-in user (already enforced).
 */
if ( ! defined('ABSPATH') ) { exit; }
 
$pod = function_exists('pods') ? pods( get_post_type(), get_the_ID() ) : null;
if ( ! $pod ) {
    wp_die('Document data unavailable.');
}
 
$doc_type = get_post_type();
 
// --- 1. Public flag (uncomment after creating the field) ---
$allow_public_view = false;
/*
$allow_public_view = (bool) $pod->field('allow_public_view');
*/
 
// --- 2. Require login unless public ---
if ( ! $allow_public_view && ! is_user_logged_in() ) {
    auth_redirect(); // redirects & exits
    exit;
}
 
$current_user = wp_get_current_user();
$is_admin     = current_user_can('administrator');
 
// Collect intended access controls (may be empty if fields not defined)
$associated_user_id  = 0;
/*
$associated_user_id = (int) $pod->field('associated_user.ID');
*/
$associated_groups_ids = [];
/*
$associated_groups_ids = (array) $pod->field('associated_groups'); // adjust depending on Pods storage
*/
$associated_roles = [];
/*
$raw_roles = $pod->field('associated_roles');
$associated_roles = is_array($raw_roles) ? $raw_roles : ( $raw_roles ? [ $raw_roles ] : [] );
*/
 
// --- 3–5. Conditional enforcement (skip if public or admin) ---
if ( ! $allow_public_view && ! $is_admin ) {
 
    // 3. Exact user ownership
    if ( $associated_user_id ) {
        if ( get_current_user_id() !== $associated_user_id ) {
            wp_die('Not authorized (owner mismatch).');
        }
    }
    // 4. Group membership (placeholder – implement your own check)
    elseif ( $associated_groups_ids ) {
        /*
        // Example placeholder:
        $user_group_ids = []; // TODO: fetch groups for current user.
        if ( ! array_intersect( $associated_groups_ids, $user_group_ids ) ) {
            wp_die('Not authorized (group mismatch).');
        }
        */
    }
    // 5. Role-based access
    elseif ( $associated_roles ) {
        $user_roles = (array) $current_user->roles;
        if ( ! array_intersect( $associated_roles, $user_roles ) ) {
            wp_die('Not authorized (role mismatch).');
        }
    }
    // 6. Else: already logged in so allowed.
}
 
// ---------------- DATA FIELDS (fetch from payslip pod) ----------------
$employee              = esc_html( (string) $pod->display('employee') );
$employee_id           = esc_html( (string) $pod->display('employee_id') );
$social_security       = esc_html( (string) $pod->display('social_security_number') );
$period                = esc_html( (string) $pod->display('period') );
$department            = esc_html( (string) $pod->display('department') );
$job_title             = esc_html( (string) $pod->display('job_title') );
$basic_salary          = number_format( (float) $pod->field('basic_salary'), 2 );
$federal_tax           = number_format( (float) $pod->field('federal_tax'), 2 );
$bonuses               = number_format( (float) $pod->field('bonuses'), 2 );
$social_security_tax   = number_format( (float) $pod->field('social_security_tax'), 2 );
$medicare_tax          = number_format( (float) $pod->field('medicare_tax'), 2 );
$state_tax             = number_format( (float) $pod->field('state_tax'), 2 );
$medical               = number_format( (float) $pod->field('medical'), 2 );
$dental                = number_format( (float) $pod->field('dental'), 2 );
$total_earnings        = number_format( (float) $pod->field('total_earnings'), 2 );
$total_deductions      = number_format( (float) $pod->field('total_deductions'), 2 );
$net_pay               = number_format( (float) $pod->field('net_pay'), 2 );
 
// Load theme header (includes Tailwind and other assets)
get_header();
?>
 
<div class="max-w-2xl mx-auto bg-white font-sans text-gray-900 p-6 rounded-lg shadow-lg my-8">
  <!-- Company Info -->
  <div class="text-center mb-6">
    <div class="text-white bg-blue-900 py-2 rounded-t-lg font-semibold text-lg tracking-wide">Northridge Pharmaceuticals</div>
    <div class="bg-blue-800 text-white py-1">7649F Diamond Hts Blvd</div>
    <div class="bg-blue-800 text-white py-1">San Francisco</div>
    <div class="bg-blue-800 text-white py-1 rounded-b-lg">(415) 872-9214</div>
  </div>
 
  <!-- Payslip Header -->
  <div class="text-center text-2xl font-bold text-blue-900 mb-6">STATEMENT OF MONTHLY INCOME</div>
 
  <!-- Employee Details -->
  <table class="w-full mb-6 border border-gray-300 rounded-lg overflow-hidden">
    <tbody>
      <tr class="bg-blue-50">
        <td class="font-medium py-2 px-4">Employee Name</td>
        <td class="py-2 px-4"><?php echo $employee; ?></td>
        <td class="font-medium py-2 px-4">Department</td>
        <td class="py-2 px-4"><?php echo $department; ?></td>
      </tr>
      <tr class="bg-blue-50">
        <td class="font-medium py-2 px-4">Employee ID</td>
        <td class="py-2 px-4"><?php echo $employee_id; ?></td>
        <td class="font-medium py-2 px-4">Position/Grade</td>
        <td class="py-2 px-4"><?php echo $job_title; ?></td>
      </tr>
      <tr class="bg-blue-50">
        <td class="font-medium py-2 px-4">Social Security #</td>
        <td class="py-2 px-4"><?php echo $social_security; ?></td>
        <td class="font-medium py-2 px-4">Pay Period</td>
        <td class="py-2 px-4"><?php echo $period; ?></td>
      </tr>
    </tbody>
  </table>
 
  <!-- Earnings and Deductions -->
  <table class="w-full border border-gray-300 rounded-lg mb-6">
    <thead>
      <tr class="bg-blue-700 text-white">
        <th class="py-2 px-4 font-semibold">EARNINGS</th>
        <th class="py-2 px-4 font-semibold text-right">AMOUNT</th>
        <th class="py-2 px-4 font-semibold">TAXES/DEDUCTIONS</th>
        <th class="py-2 px-4 font-semibold text-right">AMOUNT</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="bg-green-50 py-2 px-4">Basic Salary</td>
        <td class="bg-green-50 py-2 px-4 text-right">$<?php echo $basic_salary; ?></td>
        <td class="bg-red-50 py-2 px-4">Federal Tax</td>
        <td class="bg-red-50 py-2 px-4 text-right">$<?php echo $federal_tax; ?></td>
      </tr>
      <tr>
        <td class="bg-green-50 py-2 px-4">Bonuses</td>
        <td class="bg-green-50 py-2 px-4 text-right">$<?php echo $bonuses; ?></td>
        <td class="bg-red-50 py-2 px-4">Social Security Tax</td>
        <td class="bg-red-50 py-2 px-4 text-right">$<?php echo $social_security_tax; ?></td>
      </tr>
      <tr>
        <td class="bg-green-50 py-2 px-4"></td>
        <td class="bg-green-50 py-2 px-4"></td>
        <td class="bg-red-50 py-2 px-4">Medicare Tax</td>
        <td class="bg-red-50 py-2 px-4 text-right">$<?php echo $medicare_tax; ?></td>
      </tr>
      <tr>
        <td class="bg-green-50 py-2 px-4"></td>
        <td class="bg-green-50 py-2 px-4"></td>
        <td class="bg-red-50 py-2 px-4">State Tax</td>
        <td class="bg-red-50 py-2 px-4 text-right">$<?php echo $state_tax; ?></td>
      </tr>
      <tr>
        <td class="bg-green-50 py-2 px-4"></td>
        <td class="bg-green-50 py-2 px-4"></td>
        <td class="bg-red-50 py-2 px-4">Medical</td>
        <td class="bg-red-50 py-2 px-4 text-right">$<?php echo $medical; ?></td>
      </tr>
      <tr>
        <td class="bg-green-50 py-2 px-4"></td>
        <td class="bg-green-50 py-2 px-4"></td>
        <td class="bg-red-50 py-2 px-4">Dental</td>
        <td class="bg-red-50 py-2 px-4 text-right">$<?php echo $dental; ?></td>
      </tr>
      <!-- Totals -->
      <tr class="bg-blue-100 font-semibold">
        <td>Total Earnings</td>
        <td class="text-right">$<?php echo $total_earnings; ?></td>
        <td>Total Deductions</td>
        <td class="text-right">$<?php echo $total_deductions; ?></td>
      </tr>
      <!-- Net Pay -->
      <tr>
        <td colspan="2"></td>
        <td class="bg-green-700 text-white font-bold text-right">Net Pay</td>
        <td class="bg-green-700 text-white font-bold text-right">$<?php echo $net_pay; ?></td>
      </tr>
    </tbody>
  </table>
 
  <!-- Signatures -->
  <div class="flex justify-between mt-10 text-gray-600 text-sm">
    <div class="border-t border-blue-900 pt-2 w-1/3 text-center">Employee signature:</div>
    <div class="border-t border-blue-900 pt-2 w-1/3 text-center">Director:</div>
    <div class="w-1/3"></div>
  </div>
 
  <!-- Actions -->
  <div class="mt-10 flex justify-center gap-4 print:hidden">
    <?php
      $account_page_id = (int) get_option('reportburster_account_page_id');
      $back_url = $account_page_id
        ? get_permalink($account_page_id)
        : ( get_permalink( get_page_by_path('my-documents') ) ?: home_url() );
    ?>
    <a href="<?php echo esc_url( $back_url ); ?>"
       class="inline-block px-5 py-2 rounded bg-green-600 text-white hover:bg-green-700 transition">
      Back to Documents
    </a>
    <a class="inline-block px-5 py-2 rounded bg-blue-600 text-white hover:bg-blue-700 transition"
       href="javascript:window.print();">
      Print
    </a>
  </div>
</div>
 
<?php
// Load theme footer (includes scripts and closes HTML)
get_footer();
?>

5. Adapt the "My Documents" List Template

ReportBurster Portal AI Prompt to generate the list template for custom type Caption: Use the AI prompt to generate your list template.

Update page-my-documents.php so users see their Payslips.

  • Replace the marked regions with your Payslip field list and the exact content of samples/webportal/page-my-documents-paystubs.php.
  • Add layout details as needed, then pass the prompt to your AI chat vendor.
  • Paste the returned code into your updated page-my-documents.php file.
<?php
/**
 * My Payslips (Payslips list)
 * Plain PHP (WordPress, Pods Framework, Tailwind CSS)
 * Features:
 *  - Requires login.
 *  - Lists payslips user may view.
 *  - Ownership via Pods User Relationship field: associated_user (if present).
 *  - Pagination (?page=N) & search (?q=term) on employee name, period, or department.
 *  - Graceful when ownership field not yet defined.
 */
 
if ( ! defined('ABSPATH') ) { exit; }
if ( ! is_user_logged_in() ) { auth_redirect(); exit; }
 
$current_user     = wp_get_current_user();
$current_user_id  = (int) $current_user->ID;
$user_roles       = (array) $current_user->roles;
$is_admin         = current_user_can('administrator');
$is_employee      = in_array('employee', $user_roles, true);
 
$post_type        = 'payslip';
$ownership_field  = 'associated_user'; // Pods User Relationship field name
$per_page         = 15;
$page_param       = 'page';
$search_param     = 'q';
 
$paged        = max( 1, (int) ( $_GET[$page_param] ?? 1 ) );
$search_term  = isset($_GET[$search_param]) ? sanitize_text_field( wp_unslash( $_GET[$search_param] ) ) : '';
$offset       = ( $paged - 1 ) * $per_page;
 
$payslips_rows   = [];
$payslips_found  = false;
$total_found     = 0;
$total_pages     = 1;
$filtered_notice = '';
 
/**
 * Detect ownership field existence via Pods schema (not meta scan).
 */
$ownership_field_exists = false;
if ( function_exists('pods_api') ) {
    $schema = pods_api()->load_pod( [ 'name' => $post_type ] );
    if ( isset( $schema['fields'][ $ownership_field ] ) ) {
        $ownership_field_exists = true;
    }
}
 
/**
 * Query only if Pods active and user role allowed.
 */
if ( function_exists('pods') && ( $is_employee || $is_admin ) ) {
 
    $where = [];
 
    if ( $ownership_field_exists ) {
        if ( ! $is_admin ) {
            $where[] = "{$ownership_field}.ID = {$current_user_id}";
            $filtered_notice = '(Filtered to your payslips)';
        } else {
            $filtered_notice = '(Admin – unfiltered)';
        }
    } else {
        if ( ! $is_admin ) {
            // Hide list from non-admins until ownership field defined
            $where[] = "ID = 0";
            $filtered_notice = '(Ownership field missing)';
        } else {
            $filtered_notice = '(Admin – ownership field missing, list unfiltered)';
        }
    }
 
    if ( $search_term !== '' ) {
        $like   = '%' . esc_sql( $search_term ) . '%';
        $where[] = "( employee LIKE '{$like}' OR period LIKE '{$like}' OR department LIKE '{$like}' )";
    }
 
    $params = [
        'limit'   => $per_page,
        'offset'  => $offset,
        'orderby' => 'post_date DESC',
    ];
    if ( $where ) {
        $params['where'] = implode( ' AND ', $where );
    }
 
    $pod = pods( $post_type, $params );
 
    if ( $pod ) {
        $total_found    = (int) $pod->total();
        $payslips_found = $total_found > 0;
        $total_pages    = max( 1, (int) ceil( $total_found / $per_page ) );
 
        if ( $payslips_found ) {
            while ( $pod->fetch() ) {
                $pid         = (int) $pod->id();
                $title       = get_the_title( $pid );
                $employee    = (string) $pod->display( 'employee' );
                $period      = (string) $pod->display( 'period' );
                $department  = (string) $pod->display( 'department' );
                $job_title   = (string) $pod->display( 'job_title' );
                $basic_salary = (float) $pod->field( 'basic_salary' );
                $net_pay     = (float) $pod->field( 'net_pay' );
                $date        = get_the_date( 'Y-m-d', $pid );
 
                $payslips_rows[] = [
                    'title'      => esc_html( $title ),
                    'employee'   => esc_html( $employee ),
                    'period'     => esc_html( $period ),
                    'department' => esc_html( $department ),
                    'job_title'  => esc_html( $job_title ),
                    'basic_salary' => $basic_salary ? '$' . number_format( $basic_salary, 2 ) : '',
                    'net_pay'    => $net_pay ? '$' . number_format( $net_pay, 2 ) : '',
                    'date'       => esc_html( $date ),
                    'link'       => esc_url( get_permalink( $pid ) ),
                ];
            }
        }
    }
}
 
/**
 * Simple pagination
 */
function my_payslips_paginate( int $current, int $total, string $param = 'page' ): void {
    if ( $total < 2 ) return;
    echo '<nav class="flex justify-center mt-4 space-x-2 text-sm">';
    for ( $i = 1; $i <= $total; $i++ ) {
        $url = esc_url( add_query_arg( $param, $i ) );
        if ( $i === $current ) {
            echo '<span class="px-3 py-1 rounded bg-blue-600 text-white font-semibold">'.$i.'</span>';
        } else {
            echo '<a href="'.$url.'" class="px-3 py-1 rounded bg-gray-100 hover:bg-blue-100 text-blue-700">'.$i.'</a>';
        }
    }
    echo '</nav>';
}
 
// Load theme header (includes Tailwind and other assets)
get_header();
?>
 
<div class="max-w-3xl mx-auto py-8">
  <div class="flex justify-between items-center mb-6">
    <div class="text-sm text-gray-700">
      Logged in as <strong><?php echo esc_html( $current_user->display_name ); ?></strong>
    </div>
    <a class="text-red-600 hover:underline text-sm" href="<?php echo esc_url( wp_logout_url( home_url() ) ); ?>">Logout</a>
  </div>
 
  <h1 class="text-2xl font-bold mb-2">My Payslips</h1>
  <p class="text-sm text-gray-600 mb-4">
    Payslips you are authorized to view.
    <?php if ( $filtered_notice ) : ?>
      <span class="inline-block bg-blue-100 text-blue-800 px-2 py-0.5 rounded ml-2 text-xs"><?php echo esc_html( $filtered_notice ); ?></span>
    <?php endif; ?>
    <?php if ( $search_term !== '' ) : ?>
      <span class="inline-block bg-yellow-100 text-yellow-800 px-2 py-0.5 rounded ml-2 text-xs">Search:<?php echo esc_html( $search_term ); ?></span>
    <?php endif; ?>
  </p>
 
  <div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
    <div class="flex items-center justify-between mb-4">
      <h2 class="text-lg font-semibold mb-0">Payslips
        <?php if ( $payslips_found ): ?>
          <span class="ml-2 text-xs text-gray-500">(<?php echo (int) $total_found; ?> total)</span>
        <?php endif; ?>
      </h2>
      <form method="get" class="flex gap-2 items-center">
        <input
          type="text"
          name="<?php echo esc_attr( $search_param ); ?>"
          value="<?php echo esc_attr( $search_term ); ?>"
          placeholder="Search employee, period, or department..."
          class="border border-gray-300 rounded px-3 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-200"
        />
        <button type="submit" class="px-3 py-1 rounded bg-blue-600 text-white text-sm hover:bg-blue-700">Search</button>
        <?php if ( $search_term !== '' ): ?>
          <a href="<?php echo esc_url( remove_query_arg( $search_param ) ); ?>" class="ml-2 text-blue-600 hover:underline text-xs">Reset</a>
        <?php endif; ?>
      </form>
    </div>
 
    <?php if ( $payslips_found ): ?>
      <div class="overflow-x-auto">
        <table class="min-w-full border border-gray-200 rounded-lg">
          <thead>
            <tr class="bg-gray-50">
              <th class="px-4 py-2 text-left text-xs font-semibold text-gray-700">Employee</th>
              <th class="px-4 py-2 text-left text-xs font-semibold text-gray-700">Period</th>
              <th class="px-4 py-2 text-left text-xs font-semibold text-gray-700">Department</th>
              <th class="px-4 py-2 text-left text-xs font-semibold text-gray-700">Job Title</th>
              <th class="px-4 py-2 text-right text-xs font-semibold text-gray-700">Basic Salary</th>
              <th class="px-4 py-2 text-right text-xs font-semibold text-gray-700">Net Pay</th>
              <th class="px-4 py-2 text-left text-xs font-semibold text-gray-700">Date</th>
              <th class="px-4 py-2 text-center text-xs font-semibold text-gray-700" style="width:60px;">View</th>
            </tr>
          </thead>
          <tbody>
          <?php foreach ( $payslips_rows as $row ): ?>
            <tr class="border-b last:border-b-0 hover:bg-gray-50">
              <td class="px-4 py-2"><?php echo $row['employee']; ?></td>
              <td class="px-4 py-2"><?php echo $row['period']; ?></td>
              <td class="px-4 py-2"><?php echo $row['department']; ?></td>
              <td class="px-4 py-2"><?php echo $row['job_title']; ?></td>
              <td class="px-4 py-2 text-right"><?php echo $row['basic_salary']; ?></td>
              <td class="px-4 py-2 text-right"><?php echo $row['net_pay']; ?></td>
              <td class="px-4 py-2"><?php echo $row['date']; ?></td>
              <td class="px-4 py-2 text-center">
                <a href="<?php echo $row['link']; ?>" class="text-blue-600 hover:underline">View</a>
              </td>
            </tr>
          <?php endforeach; ?>
          </tbody>
        </table>
      </div>
      <?php my_payslips_paginate( $paged, $total_pages, $page_param ); ?>
    <?php else: ?>
      <div class="py-6 text-center text-gray-500 italic">
        <?php if ( $search_term !== '' ): ?>
          No payslips match “<?php echo esc_html( $search_term ); ?>.
        <?php elseif ( ! $is_employee && ! $is_admin ): ?>
          No payslips available for your role.
        <?php else: ?>
          No payslips found.
        <?php endif; ?>
      </div>
    <?php endif; ?>
  </div>
</div>
 
<?php
// Load theme footer (includes scripts and closes HTML)
get_footer();
?>

6. Automate Payslips Publishing with ReportBurster

ReportBurster Portal AI Prompt to generate the script to publish documents on the portal Caption: Use the AI prompt to generate your publishing script.

Generate a Groovy script to publish documents to the portal via the API.

  • What the script must do:
    1. Check if the target portal user exists (by username/email); create if missing.
    2. Prepare document data for your content type.
    3. Publish the document via REST API (with authentication).

Provide your Payslip content type definition and the full code of the example script (scripts/burst/samples/curl_paystub2portal.groovy) to the AI.
Paste the returned code into your scripts/burst/endExtractDocument.groovy file.

Tip:
Guard your code with:

if (ctx.settings.getTemplateName().toLowerCase().indexOf("payslips2portal") != -1) {
  // AI-generated code here
}

so it only runs for Payslip documents.

ReportBurster Portal generate Groovy publishing script using AI Caption: Paste your AI-generated code into the publishing script.

Now, your Payslip documents and their corresponding Employee users will be uploaded to ReportBurster2Portal automatically.

ReportBurster Portal Payslips2Portal execution Caption: Automated Payslips publishing in action.

Employee users are created automatically in the portal admin.

Portal admin showing new users Caption: New users created for Payslips.

All employee Payslips are uploaded to the portal admin.

Portal admin showing new payslips Caption: Payslips uploaded to the portal.

ReportBurster works with any reporting or business software, including Crystal Reports, SAP, Oracle, Microsoft Dynamics, and more.

You can generate reports and upload to ReportBurster2Portal from any datasource.

7. Log In and Check Payslips as an Employee User

Now, experience the portal as your employees do.

Payslips are secure—each user sees only their own documents.

User login screen Caption: Employee login screen.

View your Payslips on the My Documents page.

My Documents page for payslips Caption: Payslips listed for the logged-in employee.

Click a Payslip to see its details.

Single payslip view Caption: Viewing a single Payslip.

8. Tips & Troubleshooting

  • Field names must match: Make sure your content type fields and script fields match exactly.
  • Ownership matters: The associated_user field must be set for access control to work.
  • Template errors: If you see blank pages, check your PHP templates for typos.
  • REST API issues: Double-check authentication and endpoint URLs.
  • Need more help? See the AI-Driven Portal Setup & Customizations guide.

You’ve now built a full-featured, secure Payslips portal—end-to-end, with automation and user protection!