Skip to main content

Generate Custom PDFs in Oracle APEX with jsPDF

Generate Custom PDFs in Oracle APEX with jsPDF

A complete step-by-step guide to building a client-side PDF generator with profile image embedding and PL/SQL database persistence.

📄 Step-by-step guide
Oracle APEXjsPDFDynamic ActionsPL/SQLJavaScript
"Picture this: your Oracle APEX application is polished, your users love it — but when they ask for a downloadable report with a profile picture and custom styling, you realize vanilla APEX isn't built for that out of the box. What if a single JavaScript library and one Dynamic Action could change everything?"

Welcome to the world of jsPDF inside Oracle APEX. In this tutorial, we wire up a PDF generator that captures a user's name, email, phone number, and profile picture — renders them into a beautiful PDF layout — and saves everything to Oracle, all in one button click.

What you'll build: A dynamic PDF generator with user profile details and an embedded image, powered by jsPDF in the browser with PL/SQL database persistence on submit.

1Create Page Items

Navigate to Page 4 in your APEX application and create the following four page items. These will collect all the user information needed for the PDF.

P4_NAME
Page Item
Text Field
P4_EMAIL
Page Item
Text Field
P4_PHONE_NUMBER
Page Item
Text Field
P4_PROFILE_PIC
Page Item
Image Upload ★

The P4_PROFILE_PIC item with type Image Upload is the key ingredient — it exposes a native file input that jsPDF uses to embed the image via the FileReader API.

2Load jsPDF via JavaScript File URL

In Page Designer, go to Page 4 → JavaScript → File URLs and add the following CDN URL. This loads jsPDF as a UMD global available as window.jspdf — no npm, no bundler required.

🔗 https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js

3Create the Generate PDF Button

Add a button to the page with the following settings, then attach two True Actions via a Dynamic Action on button click.

-- Button Configuration
Name   : Generate_PDF
Action : Defined by Dynamic Action
True Action 1
Execute JavaScript Code — PDF Generation
// Import jsPDF from the global window object
var { jsPDF } = window.jspdf;

// Gather form values from page items
var ename = $v('P4_NAME');
var email = $v('P4_EMAIL');
var phone = $v('P4_PHONE_NUMBER');

// Function to generate and download the PDF
function generatePDFWithImage(imgDataURL) {
var doc = new jsPDF();

// PAGE DIMENSIONS
var pageWidth = doc.internal.pageSize.getWidth();

// MAIN HEADING
doc.setFont("helvetica", "bold");
doc.setFontSize(24);
doc.setTextColor(10,10,80);
var mainTitle = "My Custom PDF Report";
var textWidth = doc.getTextWidth(mainTitle);
var xPos = (pageWidth - textWidth) / 2;
var yPos = 18;
doc.text(mainTitle, xPos, yPos);

// SECTION BORDER
var sectionX = 15,sectionY = 30,sectionW = 180,sectionH = 80;
doc.setDrawColor(0,150,200);
doc.setLineWidth(1);
doc.rect(sectionX, sectionY, sectionW, sectionH);

// SECTION TITLE
doc.setFontSize(24);
doc.setTextColor(40,40,120);
doc.text("User Profile", sectionX + 5, sectionY + 12);

// USER DETAILS
var leftX = sectionX + 8;
var valX = leftX + 35;
var startY = sectionY + 25;
var lineHeight = 14;

doc.setFontSize(12);
doc.setTextColor(0,102,204);
doc.text("Name:", leftX, startY);
doc.setTextColor(60,60,60);
doc.text(ename, valX, startY);

doc.setTextColor(0,102,204);
doc.text("Email:", leftX, startY + lineHeight);
doc.setTextColor(60,60,60);
doc.text(email, valX, startY + lineHeight);

doc.setTextColor(0,102,204);
doc.text("Phone:", leftX, startY + 2 * lineHeight);
doc.setTextColor(60,60,60);
doc.text(phone, valX, startY + 2 * lineHeight);

// PROFILE IMAGE
var imgW = 50;
var imgH = 50;
var imgX = sectionX + sectionW - imgW - 10;
var imgY = sectionY + 15;

if(imgDataURL) {
  doc.setDrawColor(160,160,160);
  doc.rect(imgX - 2, imgY - 2, imgW + 4, imgH + 4);
  doc.addImage(imgDataURL, 'JPEG', imgX, imgY, imgW, imgH);
} else {
  doc.setFontSize(11);
  doc.setTextColor(140,140,140);
  doc.text("No Profile Picture", imgX, imgY + imgH / 2);
}

// VERTICAL SEPARATOR
var sepX = imgX - 13;
doc.setDrawColor(200,220,255);
doc.line(sepX, sectionY + 8, sepX, sectionY + sectionH - 8);

// DOWNLOAD PDF
doc.save("UserDetails.pdf");
}

// READ IMAGE FIRST (THIS PART MOVED OUTSIDE FUNCTION)
var fileInput = document.getElementById('P4_PROFILE_PIC');
if (fileInput && fileInput.files && fileInput.files[0]) {
  var reader = new FileReader();
  reader.onload = function(e) {
    generatePDFWithImage(e.target.result);
  };
  reader.readAsDataURL(fileInput.files[0]);
} else {
  generatePDFWithImage(null);
}
True Action 2
Execute JavaScript Code — Page Submit
apex.page.submit({
    request: 'SUBMIT',
    validate: true
});

4Create the PL/SQL Process

In Page Designer, go to Processing → Processes → Create. Set the type to PL/SQL Code, point to After Submit, and paste the following:

DECLARE
  l_blob     BLOB;
  l_filename VARCHAR2(255);
  l_mime     VARCHAR2(128);
BEGIN
  SELECT blob_content, filename, mime_type
    INTO l_blob, l_filename, l_mime
    FROM apex_application_temp_files
   WHERE name = :P4_PROFILE_PIC; -- Replace with your item name

  INSERT INTO USER_PROFILE_DETAILS (
        USER_NAME,
        USER_EMAIL,
        USER_PHONE_NUMBER,
        USER_PROFILE,
        PROFILE_MIME,
        PROFILE_NAME
    )
  VALUES (:P4_NAME,:P4_EMAIL,:P4_PHONE_NUMBER,l_blob, l_mime, l_filename);
END;

apex_application_temp_files is a built-in APEX view that temporarily stores uploaded files during a session. We query it using the page item name as the file identifier key.

End-to-end flow

Here's what happens the moment the user clicks Generate PDF:

1
Fill form items
2
FileReader reads image
3
jsPDF builds layout
4
doc.save() downloads
5
Page submits
6
PL/SQL inserts BLOB

Tips & customization ideas

  • 🎨
    Change RGB values in setTextColor() and setDrawColor() to match your brand palette.
  • 📄
    Use doc.addPage() to add additional pages and continue rendering beyond the first page.
  • 🖌
    Load TTF fonts into jsPDF with doc.addFont() for non-Latin characters and custom brand typography.
  • 📊
    Add the jsPDF-AutoTable plugin (also available on CDN) for dynamic, styled data tables inside the PDF.
  • 🔒
    Add APEX page validations so Name, Email, and Phone are required before the button fires the Dynamic Action.

You're all set.

No server rendering, no paid plugins, no complex configuration. Clean JavaScript, a few page items, and the power of the browser's native APIs — delivering a premium download experience right inside Oracle APEX.

Oracle APEXjsPDF 2.5.1Dynamic ActionsPL/SQLFileReader API

Comments

Popular posts from this blog

APEX Custom Auth Settings, Decoded!

Oracle APEX Security PL/SQL APEX Custom Auth Settings, Decoded Why I Build this I was building an internal APEX app — strictly for people on our corporate network. The default authentication worked, but it had a few things that kept bothering me. Anyone with valid credentials could log in from anywhere. Sessions expired with a useless error page. There was no audit trail — no way to know who logged in, when, or from where. And passwords were stored in plain text, which I just couldn't leave alone. So I did what any developer does — I built it myself. Custom authentication, from scratch, in PL/SQL. Turned out to be one of the best learning experiences I've had with APEX. Here's exactly how I did it. Img 1 : Head to Shared Components → Authentication Schemes → Create, choose Custom as the Scheme Type, and plug in your function and procedure names. Quick note before we get into the code — the s...

Record, Preview, Save — Video Recording in Oracle APEX

Oracle APEX · Video Recording Record Video Directly in Oracle APEX 🎬 Have you ever wished your users could say it instead of type it? In many modern applications, we've seen the ability to record a short video clip directly in the browser — no file uploads, no external tools. Just hit record, watch it back, and decide — save it or try again. Today, we're bringing that same experience into Oracle APEX: record, pause/resume, preview, save to DB, or retry. Let's build it. 1 Step 1 : Start by creating a Static Content Region on your APEX page. This will act as the container for our entire video recorder UI.Paste the provided HTML code into the region source. This gives you two video panels — one for the live camera preview and one for the recorded playback — along with your action buttons: Start, Pause, Resume, Stop, Save, and Retry. Copy <!-- Recorder UI --> < div id= "recorderUI...