The Real Story: How SampleHQ Automates Salesforce Metadata from WordPress
November 18, 2025 • Bojan
November 18, 2025 • Bojan

Most Salesforce integrations brag about “syncing contacts” or “pushing deals.”
Cute. Basic. The easy part.
The hard part-the part nobody publicly writes about-is this:
This is the side of Salesforce that usually requires a “team of consultants”.
I didn’t want a team. I wanted a pipeline.
So SampleHQ treats Salesforce metadata the same way you treat code:
Versioned → generated → deployed → tested → re-deployed → repeatable.
What follows is the exact, real code that makes all of this work.
The goal was simple:
If the VF page doesn’t exist, create it.
If it exists, skip it.
And never touch Salesforce manually.
Here’s the core block from Installer.php:
private static function ensure_visualforce_page(SalesforceClient $client, string $instance_url, string $access_token): array {
$page_name = 'SampleHQ_Picker';
$soql = sprintf(
"SELECT Id, Name FROM ApexPage WHERE Name = '%s' LIMIT 1",
addslashes($page_name)
);
$query = self::salesforce_tooling_query($instance_url, $access_token, $soql);
if (!empty($query['data']['records'])) {
return [
'ok' => true,
'id' => (string) ($query['data']['records'][0]['Id'] ?? ''),
'name' => $page_name,
'created' => false,
];
}
$metadata_payload = [
'fullName' => $page_name,
'label' => 'SampleHQ Picker',
'markup' => self::build_visualforce_markup(),
'apiVersion' => self::get_salesforce_api_version_number(),
'availableInTouch' => false,
'confirmationTokenRequired' => false,
];
$response = $client->metadataCreate('ApexPage', $metadata_payload);
if (!$response['ok']) {
return ['ok' => false, 'error' => 'Failed to create Visualforce page: ' . ($response['error'] ?? 'Unknown')];
}
$created_lookup = self::salesforce_tooling_query($instance_url, $access_token, $soql);
$records = $created_lookup['data']['records'] ?? [];
return [
'ok' => !empty($records),
'id' => $records[0]['Id'] ?? '',
'name' => $page_name,
'created' => true,
];
}
The part I love is this:
The entire Visualforce markup is generated in PHP – not stored in Salesforce.
Edit your VF UI → commit → deploy.
If Salesforce rejects it, you see the raw error.
No more “Open Dev Console and try again.”
The second piece of the pipeline:
Create the “Link SampleHQ Orders” Quick Action automatically.
The relevant block:
private static function ensure_quick_action(
SalesforceClient $client,
string $instance_url,
string $access_token,
string $page_name
): array {
$developer_name = 'SampleHQ_LinkSampleOrders';
$label = 'Link SampleHQ Orders';
$soql = sprintf(
"SELECT Id FROM QuickActionDefinition WHERE DeveloperName = '%s' LIMIT 1",
addslashes($developer_name)
);
// Already exists? Done.
$query = self::salesforce_tooling_query($instance_url, $access_token, $soql);
if (!empty($query['data']['records'])) {
return [
'ok' => true,
'id' => (string) ($query['data']['records'][0]['Id'] ?? ''),
'developer_name' => $developer_name,
'label' => $label,
'created' => false,
];
}
// Deploy via Metadata API
$deployer = new MetadataDeployer();
$deploy_result = $deployer->deployQuickAction(
$access_token,
$instance_url,
$developer_name,
$label,
$page_name
);
The XML looks like something Salesforce consultants would charge $10K to “configure”:
<QuickAction xmlns="http://soap.sforce.com/2006/04/metadata">
<label>Link SampleHQ Orders</label>
<type>VisualforcePage</type>
<page>SampleHQ_Picker</page>
<optionsCreateFeedItem>false</optionsCreateFeedItem>
<height>400</height>
<width>600</width>
</QuickAction>
But here – it’s just a string in PHP.
You edit it the same way you edit your theme.
Deploy it using code.
Rollback with Git.
This is how Salesforce should work.
Salesforce layouts are encrypted inside SOAP responses, base64-encoded, zipped, and stored under paths that sometimes get URL-encoded for no reason.
So here’s what SampleHQ does:
.layout file (even if it’s renamed)This is the part where most engineers give up.
But this is the part that makes the whole system bulletproof.
Here’s the high-level method:
public function addQuickActionToLayout(
string $access_token,
string $instance_url,
string $layout_full_name,
string $quick_action_full_name
): array {
$retrieve = $this->retrieveLayout($access_token, $instance_url, $layout_full_name);
if (!$retrieve['ok']) return $retrieve;
$modified = $this->addQuickActionToLayoutXml($retrieve['xml'], $quick_action_full_name);
if ($modified === null) return ['ok' => false, 'error' => 'Failed to modify Layout XML'];
return $this->deployLayout($access_token, $instance_url, $layout_full_name, $modified);
}
The SOAP retrieval:
'<m:retrieve><m:retrieveRequest><m:apiVersion>65.0</m:apiVersion><m:singlePackage>true</m:singlePackage>'
Polling:
'<m:checkRetrieveStatus><m:asyncProcessId>%s</m:asyncProcessId></m:checkRetrieveStatus>'
The extraction even handles Salesforce’s bizarre filename encoding:
private function normalizeLayoutSignature(string $layout_full_name): string {
return strtolower(preg_replace('/[^a-zA-Z0-9]/', '', rawurldecode($layout_full_name)));
}
And the XML mutation:
$existing = $xpath->query(
sprintf('//md:quickActionListItems/md:quickActionName[text()="%s"]',
htmlspecialchars($quick_action_full_name))
);
if ($existing->length > 0) return $layout_xml;
If it doesn’t exist, we append:
<quickActionListItems>
<quickActionName>Opportunity.SampleHQ_LinkSampleOrders</quickActionName>
</quickActionListItems>
Then redeploy the ZIP and poll the metadata API until Salesforce approves it.
This is layout-as-code.
No dragging buttons ever again.
Because this is how you:
And most importantly:
Because when everything is code-driven, the entire installer can run inside a scratch org during automated tests.
Backed by the Salesforce CLI:
sf org create scratch -f config/scratch-platform.json
sf org create scratch -f config/scratch-enterprise.json
sf org create scratch -f config/scratch-professional.json
Backed by MCP:
This is what CRM integration looks like when you stop thinking “plugin” and start thinking “infrastructure.”

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.