Bojan Josifoski < wp developer />

Technical Deep Dive: How SampleHQ Creates Salesforce Objects Programmatically Using the Metadata API

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.


1. The Trigger: OAuth Callback

The entire schema deployment begins the moment the Salesforce OAuth flow completes.

File
plugin/src/Integration/Salesforce/Installer.php

Method
oauth_callback()

Flow

  1. Exchange authorization code for access/refresh tokens.
  2. Store connection metadata.
  3. Instantiate SalesforceClient.
  4. Call $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.


2. Schema Definition: The Schema Manager

The SalesforceClient class is responsible for describing the desired state of Salesforce.

File
plugin/src/Services/CRM/SalesforceClient.php

Method
deploySchema()

The Logic

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.


3. The Heavy Lifter: MetadataDeployer

All XML generation and Metadata API calls live inside MetadataDeployer.php.

File
plugin/src/Integration/Salesforce/MetadataDeployer.php

Method
deployCustomObject()

XML Generation

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>

Deployment Process

  1. Zip Creation
    • objects/Sample_Order__c.object
    • package.xml
  2. Metadata API Request
    POST multipart ZIP to:
    /services/data/vXX.X/metadata/deployRequest
  3. Polling
    The deployment is asynchronous, so the code polls until the job is complete.

4. Key Technical Challenges Solved

A. Idempotency

The system never blindly redeploys. It only creates what is missing.
This ensures:

B. Field-Level Security (FLS)

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:

C. Lookup Fields

Lookup fields require extra metadata attributes:

The MetadataDeployer automatically injects these based on the field definition array.


5. The Permission Set Issue We Encountered

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:

  1. Generate a Permission Set XML file dynamically.
  2. Deploy it using the Metadata API.
  3. Assign it to the connected user via REST.

After that change, field writes succeeded consistently across all Salesforce editions, including those with strict access rules.


Summary

Programmatically creating Salesforce objects is possible, but it requires navigating the Metadata API, permissions, XML definitions, and deployment quirks.

In SampleHQ:

The result is a fully automated, zero-touch Salesforce setup that behaves like a native part of the platform.

About the Author

About the Author

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.

← Back to Blog