We Used Claude Code to Migrate a Salesforce App to Oracle APEX — Here’s How

A client came to us with what sounded like a contained project: migrate their Salesforce field service app to Oracle APEX. Two months. No rush until you look at the scope.

The app was the operational backbone of their field service operation. Technicians used it daily to receive work orders, schedule visits to client sites, log crew hours, and record the materials consumed on each job. Preventive maintenance schedules ran through it. Inventory tracking ran through it. Dispatchers lived in it. It wasn’t a reporting tool or a nice-to-have; it was how the business ran.

The motivation was twofold. Salesforce licensing costs had become hard to justify for the scope of what the app actually did. And the client needed capabilities that Oracle APEX handles better by design.

Their end users needed reports they could configure themselves, filtering, column selection, aggregations, and export, without going back to a developer. APEX Interactive Grids do this out of the box. They also wanted a more open customization model: APEX lets you incorporate any JavaScript library directly, build custom plug-ins, and tap into a growing ecosystem of open-source community plug-ins ready to install and use. Salesforce allows third-party JavaScript libraries in Lightning Web Components, but they must pass Lightning Locker or Lightning Web Security validation; not all libraries are compatible, and those that aren’t require forking and patching. For a team that wanted to extend the application freely and keep ongoing costs predictable, APEX was the more practical foundation.

So the destination was clear. The path was not there. There was no Salesforce developer environment to work in, no handover documentation, and no one from the original implementation available to answer questions. What I had was the source code repository and access to the running application.

Twenty-six pages. Seventeen tables. A business logic layer I had never seen before, written in Apex, Salesforce’s language, which shares only its name with Oracle APEX and nothing else.

What made this project survivable, honestly, what made it possible at the pace we needed, was using AI as a co-pilot throughout every stage. Claude Code was my primary tool, though the approach works with any capable coding AI like GitHub Copilot or Codex. This post walks through exactly how I structured that collaboration, what worked, what didn’t, and how you can adapt it for your own migration.

The setup

Before writing a single line of Oracle code, I configured a workspace that would let the AI and me work effectively together:

  • VS Code as the primary IDE
  • Claude Code extension for VS Code (the AI layer)
  • Oracle SQL Developer extension for VS Code (database connectivity and project management)
  • SQLcl with Oracle SQL Projects (version-controlled database scripts)
  • A dedicated GitHub repository for the migration project

The SQLcl project structure is worth highlighting: it gives you a folder-based layout for your database objects (tables, packages, sequences, triggers) that deploys in dependency order. When the AI generates scripts, they land in the right place automatically.

Step 1: Understanding the Salesforce source code

My first problem was that I didn’t speak Salesforce. The repository had files and folders I had never navigated before, and without understanding the structure, I couldn’t know what to migrate or where to find it.

How a Salesforce repository is organized

A standard Salesforce DX project follows a predictable layout under force-app/main/default/. Here’s what each folder contains and why it matters for migration:

  • objects/ — The data layer. Each subfolder is a Salesforce object (equivalent to a database table). Inside you’ll find the object’s fields, validation rules, and relationships defined in XML. Custom objects end with c (e.g., WorkOrder c). This is where you extract your ER model.
  • classes/ — The business logic layer. These .cls files are Apex classes — Salesforce’s Java-like language. Controllers, services, utility classes, and trigger handlers all live here. Each class has a companion .cls-meta.xml file with deployment metadata.
  • triggers/ — Database-level event handlers (before/after insert, update, delete). Similar in concept to Oracle triggers, but written in Apex.
  • lwc/ — The UI layer. Lightning Web Components are the modern Salesforce frontend framework. Each component is a folder containing an .html template, a .js controller, and a .js-meta.xml config file. One component typically maps to one screen or reusable UI element.
  • aura/ — An older UI framework you may still encounter alongside LWC in legacy apps. Same concept, different syntax.
  • permissionsets/ and profiles/ — Security and access control definitions. Useful context for understanding what roles the app supports.
  • flows/ — Salesforce Flow automations. These are visual process builders that encode business logic outside of Apex, easy to miss if you’re only looking at code files.

The naming convention is your navigation system: a component called workOrderDetail in lwc/ will typically call a class called WorkOrderDetailController.cls in classes/, which in turn queries the WorkOrder c object defined in objects/. Once you see the pattern, the whole codebase becomes readable.

Asking Claude to map it

With this structure in mind, my first Claude Code prompt was:

What came back confirmed the structure above and applied it specifically to our codebase, naming which LWC components mapped to which Apex classes, which objects they read from, and where the trigger logic lived. This took about ten minutes. Without AI, I’d have spent two days reading documentation.

One important thing I learned immediately: AI reads Salesforce code well, but it doesn’t always read it correctly. Claude mapped our WorkOrder c object relationships slightly wrong on the first pass; it missed a lookup that ran through a junction object. I caught it by comparing the map against the running app. That validation step, always checking AI output against the live application, became a discipline throughout the project.

Step 2: DB migration, understand the data before you touch anything else

Even if your Oracle schema already exists, do not skip this step. The Mermaid diagram exercise is not just about generating DDL scripts. It forces you to understand the data model at a level that every subsequent decision depends on, how entities relate, where the business rules live in the schema, and what architectural choices Salesforce made that you may want to do differently in Oracle. This is also the moment to make deliberate decisions about primary keys, constraint handling, naming conventions, and check constraints. Decisions made here echo through every package, every APEX page, and every query in the project.

If your Oracle schema is already defined, use this step to document it as a Mermaid diagram anyway. You’ll refer to it constantly.

Extract the data model from the source code

Salesforce .object files are essentially XML schema definitions. They list every field, its type, and its relationships to other objects. Ask Claude to read them all and identify the entities:

For our field service app, this surfaced entities like WorkOrder, ServiceAppointment, Asset, ServiceTerritory, and Technician, along with the relationships between them.

Generate a Mermaid ER diagram

Once you have the entity list, ask Claude to turn it into a Mermaid erDiagram. This gives you something visual you can reason about and share with a client or team member:

Make your architectural decisions now

This is the moment to review the model critically and apply your Oracle standards before any scripts are generated. Key decisions to make:

Primary keys — use GENERATED ALWAYS AS IDENTITY (Oracle 12c+) rather than sequences with triggers. It’s cleaner, requires less code, and is the modern Oracle standard.

Foreign key constraints — define them as DEFERRABLE INITIALLY DEFERRED. This allows bulk inserts and data migrations without fighting constraint ordering, while still enforcing referential integrity when transactions commit.

Check constraints — any column that Salesforce implemented as a picklist (status, priority, type) should become a CHECK constraint in Oracle. Don’t rely on the application layer alone to enforce valid values.

Naming conventions — apply them consistently before generating scripts, not after:

  • Tables: plural, uppercase (WORK_ORDERS, TECHNICIANS)
  • Primary key constraint: <table_name>_pk (e.g. work_orders_pk)
  • Foreign key constraints: <table_name>_fk01, <table_name>_fk02 (numbered sequentially)
  • Indexes: <table_name>_<column>_ix

Feed these decisions back into Claude before script generation:

Generate the DDL scripts

With a clean, reviewed diagram and clear standards, ask Claude to generate the Oracle DDL. The scripts go straight into your SQLcl project, organized by object type, ready to deploy in dependency order.

This step transforms a Salesforce data model into an Oracle schema that you actually understand and own, not one that was generated blindly and accepted without scrutiny.

Step 3: Map the application pages

Now that I understood the data model, I needed to understand the application itself, what pages existed, what each one did, and how they connected.

Claude produced a list of 18 pages for our app: work order lists, detail views, scheduling calendars, technician dashboards, and asset history screens. Each entry included the Salesforce components involved and the Apex classes they called.

This is where the live app becomes essential. I opened the running Salesforce application alongside Claude’s map and walked through every page. Claude had missed one screen entirely (an admin configuration page buried in a settings menu) and had merged two separate list views into one. I fed those corrections back:

The corrected map became the migration spec. Every subsequent step referenced it.

Step 4: Generate PL/SQL CRUD packages

Before touching business logic, I built the data access layer. For each of the 17 tables, I asked Claude to generate a PL/SQL package with standard CRUD procedures:

This produced consistent, predictable data access across all tables. APEX pages would call these packages rather than writing inline SQL, a pattern that makes the application easier to maintain and the APEX development cleaner.

Claude’s first attempt at the get_list function used a dynamic SQL approach I wasn’t happy with. I corrected it:

This back-and-forth is normal. The AI gives you 80% of the way there; you close the gap with your standards and judgment.

Step 5: Migrate the business logic

With the CRUD layer in place, I tackled the business logic, the Apex classes that contained field validation, status transitions, scheduling rules, and notification triggers.

Map the logic first

Before writing a single line of PL/SQL, ask Claude to map what logic belongs where:

This gives you a dependency picture, which classes are shared across multiple pages, which are page-specific, and which are utility layers called from everywhere.

Design the package architecture in Plan Mode

Don’t ask Claude to start generating packages immediately. Instead, use Plan Mode to design the architecture first, review it, and lock it down before any code is written.

The proposal will typically suggest grouping by functional area rather than one package per table, —a PKG_WORK_ORDER_SVC that handles all work order business logic, a PKG_SCHEDULING_SVC for appointment logic, and a PKG_NOTIFICATION_SVC for alerts. Review this critically. Things to evaluate:

Public vs private split — procedures called by APEX pages go in the spec. Internal helpers, validation sub-routines, and shared logic go in the body only. If something is in the spec without a clear external caller, push back.

Error handling strategy — establish one approach and hold to it. My preference: RAISE_APPLICATION_ERROR with a consistent error number range for business rule violations, and unhandled exceptions propagating naturally for unexpected errors. Ask Claude to confirm that the proposal follows this before approving.

Dependency order — packages that depend on other packages must be noted explicitly. The SQLcl project needs to deploy them in the right sequence, and circular dependencies need to be caught at design time, not after scripts are generated.

Once you’re satisfied with the design, approve it and then ask Claude to generate:

Translate the Salesforce logic faithfully

When Claude generates the packages, instruct it to preserve the original logic rather than simplify it:

That last instruction, flag Salesforce-specific behavior, was important. Things like Salesforce’s platform events, governor limits patterns, and trigger framework conventions don’t translate directly. Claude would mark them with — TODO: SALESFORCE-SPECIFIC — so I could review them rather than silently getting something wrong.

Step 6: Generate the APEX application

With the database objects and PL/SQL packages in the SQLcl project, I had Claude plan the APEX application before generating any scripts:

I reviewed the plan, pushed back on two page type choices (Claude suggested a classic report where I wanted an interactive grid), and then approved it:

What actually happened

This is the step where I want to be honest: Claude struggled here more than anywhere else in the project.

The APEX export script format is complex; it’s a dense, highly structured SQL file that APEX’s import engine is unforgiving about. Claude’s first attempt produced a script that failed on import. The second attempt imported but had broken page references and missing region source queries. By the third iteration, after feeding Claude the specific import errors and correcting the structure incrementally, I had a script that loaded cleanly.

Even then, what I got was not an application ready to deploy. It was a solid first version, navigation structure intact, pages present, regions wired to the right package procedures, basic form items in place. But the layouts needed work, validations needed tuning, and several dynamic actions had to be built manually. I’d estimate it represented about 60% of the work done, which on a 26-page application is still a meaningful head start. Building the same skeleton by hand would have taken considerably longer.

The iteration process taught us something useful: when Claude produces a broken APEX script, don’t ask it to “fix it” in the abstract. Copy the exact import error, paste it back, and ask Claude to correct that specific issue. Targeted corrections work far better than open-ended retries.

Where is this heading?

This is the area where we expect the most meaningful improvement in the near future. Oracle APEX 26.1 is expected to introduce APEXLang, a new human-readable, text-based format for representing APEX applications. Today’s export format is a dense SQL script that AI models have to reverse-engineer and reconstruct blindly. APEXLang replaces that with structured, diffable, Git-friendly text that is far easier for both humans and AI to read, generate, and validate.

More importantly, APEXLang is designed as the foundation for what Oracle is calling GenDev, generative development, where AI can understand and produce APEX application logic from natural language. If you’re reading this after APEX 26.1 ships, the iteration cycles described above may already be a thing of the past. For now, go in expecting to work the script through a few cycles before it imports cleanly, and budget time for manual polish after import.

Step 7: Deploy

Deployment was the straightforward part, which is the point of having the SQLcl project structure:

The SQLcl project deploys tables first, then sequences, then packages, then any seed data, in dependency order. Once the database objects were in place, I imported the APEX application export through the APEX builder, made the final adjustments, and had a running first version to test against the Salesforce original.

What I’d do differently

Invest more time in the page map validation. The hours I spent walking through the live Salesforce app alongside Claude’s map paid back many times over. Every correction at that stage prevented rework later.

Push back on AI-generated PL/SQL earlier. I accepted some of Claude’s first-pass packages without scrutinizing them closely enough, and found inconsistencies when the APEX pages started exercising them. Establish your standards upfront and give Claude a reference package to follow from the beginning.

Don’t expect the APEX generation to be hands-off. The generated application script is a head start, not a finish line. Budget time for manual APEX work, roughly 30–40% of what a full manual build would take, in my experience.

The honest summary

Claude Code (or any capable coding AI) doesn’t replace your Oracle and APEX expertise. What it does is eliminate the tax of unfamiliarity, reading a codebase in a language you don’t know, translating patterns between platforms, and generating boilerplate you’d otherwise write by hand. On a 26-page, 17-table application, we estimate that the AI collaboration cut the project timeline roughly in half.

The discipline that made it work: treat every AI output as a draft, not a deliverable. Validate against the running application. Establish standards early and hold the AI to them. Correct mistakes explicitly so they don’t propagate.

If you’re sitting in front of a Salesforce repository wondering where to start, start by asking Claude (or your AI of choice) to explain it to you. You’ll be writing Oracle packages within the hour.

 

WAYKITECH
“We make technology work for you!”

Author: Gerardo Meoño, Senior Oracle APEX Developer at Waykitech

Date: May 13th, 2026