A handful of Salesforce objects look like normal SObjects when you query them, but they reject DML — insert, update, delete, or some combination. Trying anyway throws a runtime System.SObjectException like “DML operation Insert not allowed on AggregateResult.”
The list breaks into three buckets: query-only synthetic objects, system-managed objects with restricted DML, and read-only system objects.
Bucket 1: Query-only synthetic objects
These aren’t real records — they’re aggregates or virtual rows the platform builds at query time.
| Object | Why no DML |
|---|---|
AggregateResult | Result of GROUP BY/aggregate queries. Synthetic — has no underlying row. |
DescribeSObjectResult / Schema.DescribeFieldResult | Schema metadata — read-only by definition. |
OpportunityFieldHistory, AccountHistory, etc. (*History) | Audit trail rows — Salesforce writes them; you don’t. |
RecordType | Defined in Setup. Visible via SOQL but not editable through DML. |
BusinessProcess, RecordTypeLocalization | Setup-defined. |
LoginHistory | Generated by the platform on each login. |
UserRecordAccess | Calculated at query time, never stored. |
List<AggregateResult> ar = [SELECT COUNT(Id) cnt FROM Account];
// insert ar; // System.SObjectException: DML not allowed on AggregateResult
Bucket 2: System-managed with partial DML
Some objects support some DML operations but not others.
| Object | Allowed | Forbidden |
|---|---|---|
ApexClass, ApexTrigger | Created via Tooling API or metadata deploy | No direct insert in Apex |
Profile | Updatable via Tooling API | No insert or delete in Apex on standard profiles |
UserLicense | Read | Not assignable via DML |
LoginHistory | Read | No DML |
EmailMessage | Insert (via inbound email or platform) | Updates and deletes restricted |
Knowledge__kav (Salesforce Knowledge) | Insert / Update (with conditions) | Standard delete only on archived versions |
FlowInterview | Created by Flow runtime | No direct insert from user code |
These are platform-managed lifecycles — the system creates and retires them, not you.
Bucket 3: External and Big Objects
| Object | DML behaviour |
|---|---|
External objects (__x) | DML depends on the OData connection — many are read-only |
Big Objects (__b) | Use Database.insertImmediate() instead of insert; no update, no delete (replaced with a new insert) |
Custom Metadata (__mdt) | No DML at all in Apex — use Metadata.Operations.enqueueDeployment() (a metadata API call, not DML) |
Big Objects are an especially common interview gotcha because they look like normal custom objects (MyBigObj__b) but use a different API:
MyBigObj__b row = new MyBigObj__b(...);
// insert row; // SObjectException
Database.insertImmediate(new List<MyBigObj__b>{ row }); // correct
Custom Metadata Types — the really common gotcha
You can read MyConfig__mdt records freely with SOQL. You cannot insert or update them with insert/update. To modify, you go through the metadata API via Metadata.Operations.enqueueDeployment() — async, requires the Metadata API permission, returns a deployment Id.
List<MyConfig__mdt> rows = [SELECT Id, Threshold__c FROM MyConfig__mdt];
// update rows; // SObjectException — no DML on __mdt
This is by design: Custom Metadata is meant to be deployed between orgs as configuration, not edited transactionally.
How to find out for a given object
Use Schema describe:
Schema.DescribeSObjectResult d = MyObject__c.SObjectType.getDescribe();
System.debug('Creatable: ' + d.isCreateable());
System.debug('Updatable: ' + d.isUpdateable());
System.debug('Deletable: ' + d.isDeletable());
System.debug('Undeletable: ' + d.isUndeletable());
The four boolean methods on DescribeSObjectResult are the authoritative source. They reflect FLS for the running user plus the platform’s intrinsic restrictions.
What interviewers are really looking for
The naming check: AggregateResult, Custom Metadata, History objects. The senior signal is: (1) distinguishing query-only synthetic objects (AggregateResult, *History) from platform-managed lifecycles (Profile, ApexClass) from special APIs (Big Objects via insertImmediate, Custom Metadata via Metadata.Operations), (2) the isCreateable/isUpdateable/isDeletable describe methods as the runtime check, (3) Custom Metadata edits go through the Metadata API, not DML — surprises a lot of mid-level devs.
Verified against: Apex Developer Guide — DML Operations, Big Object Storage. Last reviewed 2026-05-17.