The Apex tab is two-paned. The left is a dependency tree rooted at each Apex trigger; the right is a detail view of whichever class you click.
For each enabled object Stood Flows finds the Apex triggers and walks their full dependency chain — every Apex class a trigger calls, every class those classes call, and so on. There is no depth limit: if your trigger pulls in 250 classes through six layers of helpers and utility libraries, all 250 end up in the tree. Classes reachable from several entry points are recorded once (a diamond shared by two triggers appears once, not twice).
The class bodies fetched along the way are saved locally under workspaces/<alias>/graphs/<slug>/apex/ (classes/<Name>.cls, triggers/<Name>.trigger) so subsequent analyses don't have to re-download them. Managed-package code with hidden bodies is recorded but the body is left empty — Salesforce doesn't expose it.
When you click a node, the right pane shows:
Header — class name, lines of code, dependency depth from the trigger, chips for RT / DML / SOQL / package presence.
Record-type conditions — RT names the class branches on. Detected two ways:
Quoted RT name or developer name found inside string literals.
References to constants (e.g. RTConstants.OPP_NEW_BUSINESS) where the constant value resolves to a known RT. The "constant holder" classes that define the RT constants are flagged separately so they don't count themselves as touching every RT.
DML calls — insert, update, upsert, delete, undelete plus their Database.* equivalents. The target sObject is resolved by looking back at the variable's declared type. DML on List<Account> becomes a DML call on Account.
SOQL queries — every inline [SELECT … FROM …] expression in the class. The query is stored as a full string (no truncation), tagged with the SObject in its outer FROM clause. Sub-queries inside parent queries are recognised as sub-queries and don't leak into the SObject list. Identical queries appearing in multiple classes are recorded once.
Source code — the class body, syntax-highlighted, with RT names / DML keywords / SOQL queries highlighted to match the panel state.
The little badges next to each class name in the tree:
RT — class has one or more record-type conditions.
DML — class issues at least one DML statement.
SOQL — class issues at least one SOQL query.
📦 — a migration package has been generated for this class.
These are computed once and persisted in apex-panel.json.
The Refresh Mapping button at the top of the left pane re-runs the local analysis against the saved .cls / .trigger bodies. It's quick — no org calls — and you'd reach for it in two situations:
After upgrading Stood Flows, if the panel hasn't already rebuilt the analysis on its own. (It usually does: when the app detects an older cached analysis, it triggers Refresh Mapping automatically on first open.)
If you've manually edited or renamed a class body on disk and want the panel to pick it up.
Refresh Mapping does not re-fetch from the org — that requires a full Refresh.
Click a class, then click Generate schema artefacts in the right pane header. This produces a self-contained package under:
workspaces/<alias>/graphs/<slug>/apex-packages/<className>/├── manifest.json # summary: class count, sObject count, generation time├── dependency-tree.json # the tree rooted at this class, structural├── classes/ # mirrored .cls bodies for every class in the chain│ ├── MyClass.cls│ ├── DependencyA.cls│ └── …└── sfschemas/ # one schema per SObject the chain touches├── Account.sfschema├── Opportunity.sfschema└── …
Each sfschema is the full describe of an SObject — fields, types, picklist values, relationships — followed by an access patterns block:
object Lead {Id : IdName : String(255)…}// Access patterns for Lead observed in Apex package "MyClass".// 4 SOQL query/queries:soql SOQL_Id_Name_Status_W_Status: SELECT Id, Name, Status FROM Lead WHERE Status = 'Open'soql SOQL_Id_Lim200: SELECT Id FROM Lead LIMIT 200soql SOQL_Id_Email_W_IsConverted: SELECT Id, Email FROM Lead WHERE IsConverted = falsesoql SOQL_count_GROUP_W_Source: SELECT COUNT(Id) FROM Lead GROUP BY LeadSource// DML operations:dml INSERTdml UPDATE
soql and dml are first-class statements (not comments), so a downstream team can parse them programmatically — they sit on their own lines with a stable, easy-to-match shape. The migration target is the contract: any replacement service must support these access patterns or document how it's migrated past them.
Const names are derived from the query shape:
Selected fields (first three).
WHERE column name.
LIMIT value.
Identical queries from multiple classes share one const. Near-misses (different fields, different WHERE) get distinct names.
The use case: an architect wants to replace an Apex class chain with an out-of-org service (a microservice, a worker, a separate SF org). The runtime boundary will be HTTP + a schema contract; the contract must reflect every SObject the Apex chain reads or writes today. The sfschema files give the consumer team:
The full schema of every relevant SObject.
The exact SOQL queries to support.
The DML operations to provide an equivalent for.
The Apex source bodies for reference, in case behaviour is non-obvious from the queries alone.
You hand this package off and the receiving team has everything they need to scope and validate the migration.
The Code column group in the KPI table shows aggregates per trigger:
Classes / Methods / Deps / Size — how big the trigger's reachable code is.
A trigger with Size > 200 000 characters and Deps > 100 is almost always worth extracting. Use the Apex tab to walk the tree, identify the actual business logic vs the framework/utility classes, and generate a package for the business logic only.
A class doesn't appear — it's there but the body is hidden (managed package). The tree shows the class with its dependency edges but the right pane says (hidden). SOQL/DML detection won't run.
SOQL count looks low — your class uses Database.query(soqlString) with the query built dynamically at runtime. Stood Flows can only see SOQL that's written literally in the source; dynamically-built queries aren't visible to any static analyser.
DML count looks low — same idea: dynamic DML (Database.update(genericList) where the list's SObject type is decided at runtime) isn't statically resolvable. When the SObject type can't be inferred from a variable declaration nearby, the DML is recorded against the variable name as a best-effort signal.
RT condition missed — check the constant resolution map. If your RT constants live in a class Stood Flows didn't fetch (managed package, namespaced class) the resolution can't happen.