Technical Deep Dive: How SampleHQ Creates Salesforce Objects Programmatically Using the Metadata API
November 21, 2025 • Bojan
November 21, 2025 • Bojan

When you integrate a SaaS product with Salesforce, most developers stop at syncing data through the REST API. But if you want a real product experience, the schema itself must be created automatically.
In SampleHQ, we went one step further.
When a customer connects their Salesforce account, we generate the entire Salesforce object model programmatically, including fields, lookup relationships, and permissions – with no manual setup required.
This article breaks down how the system works, the architectural decisions behind it, and one of the trickier issues we hit when assigning permissions to newly created fields.
The entire schema deployment begins the moment the Salesforce OAuth flow completes.
File
plugin/src/Integration/Salesforce/Installer.php
Method
oauth_callback()
SalesforceClient.$client->deploySchema().Example:
$client = new SalesforceClient($connection_id);
$schema_result = $client->deploySchema();
This ensures that the connected Salesforce org is immediately provisioned with the objects SampleHQ relies on.
The SalesforceClient class is responsible for describing the desired state of Salesforce.
File
plugin/src/Services/CRM/SalesforceClient.php
Method
deploySchema()
A. Check if the object exists
The system calls:
describeObject('Sample_Order__c')
If the object exists, it calculates a diff and only deploys missing fields.
B. Define all required fields
Example:
$fields = [
'Order_Number__c' => [
'type' => 'Text',
'label' => 'Order Number',
'length' => 255,
'unique' => true,
'externalId' => true,
],
'Account__c' => [
'type' => 'Lookup',
'referenceTo' => 'Account',
'relationshipName' => 'Sample_Orders_from_Account',
],
// ...
];
C. Deploy the object and fields
This is delegated to the MetadataDeployer.
D. Deploy and assign a Permission Set
This is critical because writing to the new fields often fails if the user does not have FLS access.
All XML generation and Metadata API calls live inside MetadataDeployer.php.
File
plugin/src/Integration/Salesforce/MetadataDeployer.php
Method
deployCustomObject()
The class builds the standard Salesforce CustomObject XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Sample_Order__c</fullName>
<deploymentStatus>Deployed</deploymentStatus>
<sharingModel>ReadWrite</sharingModel>
<nameField>
<label>Order Number</label>
<type>Text</type>
</nameField>
<fields>
<fullName>Order_URL__c</fullName>
<label>Order URL</label>
<type>Url</type>
</fields>
</CustomObject>
objects/Sample_Order__c.objectpackage.xml/services/data/vXX.X/metadata/deployRequestThe system never blindly redeploys. It only creates what is missing.
This ensures:
This was the most common failure point.
Problem
Salesforce allows you to create a field but does not automatically grant access to it.
The API user then tries to write to the field and receives errors like:
INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY
FIELD_INTEGRITY_EXCEPTION
Solution
We deploy a Permission Set named SampleHQ_Integration and assign it to the connected user.
Why this approach works:
Lookup fields require extra metadata attributes:
<referenceTo><relationshipName><deleteConstraint>The MetadataDeployer automatically injects these based on the field definition array.
During initial development, the object deployed successfully – but attempts to write to certain fields failed.
The root cause was subtle:
Salesforce does not apply default Field-Level Security to new fields, even for system administrators created through OAuth.
Our Metadata API deployment created the fields, but the connected user had no permission to write to them.
This caused:
Fix
We extended the deploySchema() process to:
After that change, field writes succeeded consistently across all Salesforce editions, including those with strict access rules.
Programmatically creating Salesforce objects is possible, but it requires navigating the Metadata API, permissions, XML definitions, and deployment quirks.
In SampleHQ:
SalesforceClient defines the schema.MetadataDeployer generates XML and deploys it.The result is a fully automated, zero-touch Salesforce setup that behaves like a native part of the platform.

I’m Bojan Josifoski - I’m a WordPress systems engineer who developed and maintained a proprietary WordPress-based framework used by U.S. financial institutions between 2016 and 2025.