Displaying Images in Oracle APEX Using (Oracle Cloud Infrastructure) OCI Object Storage

In modern Oracle APEX applications, storing images inside database tables is rarely the best option.

OCI Object Storage gives you scalability, better performance, and lower cost.

This article explains how to display images in Oracle APEX using only OCI Object Storage, covering:

  • Secure image access
  • Pre-Authenticated Requests (PAR)
  • Friendly URLs
  • Performance and caching
  • A complete working flow

Why OCI Object Storage for Images

OCI Object Storage is ideal for images because:

  • No database size growth
  • Built for large binary files
  • Secure by default
  • Works cleanly with APEX and ORDS
  • Supports time-bound access via PAR

OCI does not allow public access by default, which is good.

It forces you to be explicit about how images are exposed.

Two Correct Ways to Display OCI Images in APEX

There are only two production-safe approaches.

1. APEX Proxy (Secure)

Browser → APEX → OCI Object Storage
  • Image fetched by APEX using credentials
  • Streamed to browser
  • Fully session-controlled

2. Pre-Authenticated Request (PAR) (Fast)

Browser → OCI Object Storage
  • Temporary public URL
  • No APEX load
  • Best for galleries and thumbnails

We’ll cover both, starting with PAR.

Full OCI PAR Generation Code (PL/SQL)

This function generates a browser-ready PAR URL for a single object.

What it does

  • Creates an object-level PAR
  • Sets expiry in hours
  • Returns a direct URL usable in <img src>

PL/SQL Function

CREATE OR REPLACE FUNCTION get_object_par_url (
    in_region        IN VARCHAR2,   -- ap-mumbai-1
    in_namespace     IN VARCHAR2,   -- OCI namespace
    in_bucket_name   IN VARCHAR2,   -- bucket name
    in_object_name   IN VARCHAR2,   -- images/product1.jpg
    in_expiry_hours  IN NUMBER DEFAULT 24
) RETURN VARCHAR2
IS
    l_response     CLOB;
    l_access_uri   VARCHAR2(4000);
    l_expiry_ts    VARCHAR2(50);
BEGIN
    l_expiry_ts :=
        TO_CHAR(
            SYSTIMESTAMP AT TIME ZONE 'UTC'
            + NUMTODSINTERVAL(in_expiry_hours, 'HOUR'),
            'YYYY-MM-DD"T"HH24:MI:SS"Z"'
        );

    l_response :=
        apex_web_service.make_rest_request(
            p_url         => 'https://objectstorage.' || in_region ||
                             '.oraclecloud.com/n/' || in_namespace ||
                             '/b/' || in_bucket_name || '/p/',
            p_http_method => 'POST',
            p_body        => '{
                "name":"img_par_' || DBMS_RANDOM.STRING('X', 8) || '",
                "accessType":"ObjectRead",
                "objectName":"' || in_object_name || '",
                "timeExpires":"' || l_expiry_ts || '"
            }'
        );

    l_access_uri := json_value(l_response, '$.accessUri');

    RETURN 'https://objectstorage.' || in_region ||
           '.oraclecloud.com' || l_access_uri;
END;
/

Using PAR in APEX

Generate URL in SQL

SELECT get_object_par_url(
           'ap-mumbai-1',
           'a78hhjasl9djjkmz',
           'product-images',
           'products/p1001.jpg',
           6
       ) AS image_url
FROM dual;

Display in APEX

<img src="#IMAGE_URL#" alt="Product Image" loading="lazy">

#IMAGE_URL# is the URL placeholder

This is:

  • Fast
  • CDN-friendly
  • Perfect for cards, galleries, sliders

Secure Approach: Proxy OCI Images Through APEX

Direct URL is restricted for security.

Use this when:

  • Images are sensitive
  • Access depends on user role
  • You don’t want public URLs

On-Demand Application Process (APEX)

Create an Application Process named GET_OBJECT_FILE.

DECLARE
    l_blob      BLOB;
    l_mime_type VARCHAR2(100);
BEGIN
    l_blob := apex_web_service.make_rest_request_b(
        p_url         => :P0_OBJECT_URL,
        p_http_method => 'GET'
    );

    l_mime_type := apex_util.get_mime_type(:P0_FILE_NAME);

    owa_util.mime_header(l_mime_type, FALSE);
    htp.p('Content-Disposition: inline; filename="' || :P0_FILE_NAME || '"');
    owa_util.http_header_close;

    wpg_docload.download_file(l_blob);
    apex_application.stop_apex_engine;
END;

Generate Friendly URL

APEX_PAGE.GET_URL(
    p_page    => 0,
    p_request => 'APPLICATION_PROCESS=GET_OBJECT_FILE',
    p_items   => 'P0_OBJECT_URL,P0_FILE_NAME',
    p_values  => object_url || ',' || file_name
)

Use in HTML

<img src="#IMAGE_URL#" alt="Secure Image">

Complete APEX + OCI Demo Flow

Here’s the end-to-end flow most real apps use.

  1. Images uploaded to OCI bucket
  2. Image paths stored in table (only metadata)
  3. APEX page queries image paths
  4. For each image:
    • Generate PAR (external users)
    • OR proxy via APEX (internal users)
  5. Browser displays image

Image Caching Strategy (Very Important)

Without caching, image pages will feel slow.

For PAR URLs

  • OCI already supports HTTP caching
  • Keep PAR expiry ≥ image cache lifetime
  • Use browser caching
<img src="PAR_URL" loading="lazy">

Best for:

  • Product images
  • Thumbnails
  • Public galleries

For APEX-proxied Images

Add cache headers:

htp.p('Cache-Control: private, max-age=3600');
htp.p('Pragma: cache');

This allows:

  • Browser caching
  • Reduced APEX load
  • Faster page rendering

Works perfectly with:

  • PAR URLs
  • APEX proxy URLs
  • Normal APEX pages

Potential errors:

  • BucketNotFound
  • Either the bucket named <bucket_name> does not exist in the namespace <namespace> or you are not authorized to access it

Final Recommendation

Use this rule:

  • Internal or sensitive images → APEX proxy
  • Public or high-traffic images → PAR

OCI Object Storage + APEX works extremely well when used correctly.

Related articles