A salesforce custom metadata type deployment fails for a reason most admins don't expect: the records themselves are metadata, not data. That means every record you create in a sandbox travels with your deployment package by default, whether you meant it to or not. Miss that fact and you'll ship a sandbox API key, a test endpoint, or a placeholder record straight into production.

Custom metadata types were built to solve a real problem: configuration that needs to move between orgs without a data load. Custom labels do this for text. Custom settings do it for org-wide or hierarchical values. Custom metadata types do it for structured, relational configuration, like mapping tables, feature flags, or integration rules. The tradeoff is that Salesforce treats the records as part of the metadata layer, which means they're included in Metadata API retrieves and deployments unless you explicitly exclude them.

Why Custom Metadata Types Break the Sandbox-to-Production Assumption

Most admins assume a clean separation: metadata (fields, flows, objects) goes through the deployment pipeline, and data (accounts, opportunities, custom metadata records with business values) stays in each org. Custom metadata types violate that assumption on purpose. A record of type Integration_Setting__mdt with a field called Endpoint_URL__c is, technically, a piece of metadata. It deploys with the same package.xml entry as the object definition itself.

This is exactly why the type exists. Salesforce wanted a way to move configuration through a release pipeline the same way you'd move a Flow or a validation rule. The problem shows up when teams treat custom metadata records like static settings and forget they're subject to normal deployment rules, including field-level security, page layout assignments, and record-level API names that must be unique and stable across every org.

I've seen teams spend two days debugging a broken integration only to find the deployment had overwritten a production endpoint with the sandbox value from six months earlier, because nobody had touched that metadata record since, and it still had the old package.xml entry sitting in a deployment profile.

Records Are Metadata, Not Data — And That Changes Everything

Once you accept that custom metadata records deploy like Apex classes, the implications get clearer. You need version control for them. You need to know which records are safe to overwrite in production and which ones hold environment-specific values that should never leave their org. You need a package.xml strategy that either includes or excludes specific records, not just the object type.

The Metadata API lets you scope this down to individual records using the CustomMetadata component type, formatted as ObjectName.RecordName. That granularity is useful, but it's also easy to get wrong manually. Add a new record in sandbox, forget to add it to package.xml, and it simply won't deploy — no error, no warning, just a quiet gap between what you tested and what went live.

The inverse problem is worse. Include a record you didn't mean to touch, and you'll silently overwrite whatever value currently sits in production. Custom metadata deployments don't merge fields the way some other Salesforce operations do; a full record deployment replaces every field value on that record with whatever the source org has, including fields you never intended to change.

The Environment-Specific Value Problem

Here's the core tension: custom metadata types are built for values that should be identical across environments (business rules, feature toggles, mapping logic), but teams constantly use them to store values that must differ by environment (API endpoints, org IDs, callout timeouts). Both use cases are valid. Both live in the same object type. Salesforce gives you no native flag to say "deploy this field everywhere except this one."

Some teams solve this with a naming convention: fields like Endpoint_URL__c get manually re-set after every deployment, using a documented runbook. That works until someone forgets, or a new admin isn't told, or the runbook lives in a wiki nobody reads before a Friday afternoon release.

A more durable pattern is separating configuration into two custom metadata types: one for values that should always deploy identically (business logic, mapping tables), and one for environment-specific values that get set once per org and are explicitly excluded from every deployment package going forward. It adds a bit of object sprawl, but it removes the guesswork about what's safe to overwrite.

ApproachProsCons
Manual re-set after deployNo extra objects, quick to set upDepends entirely on human memory
Separate env-specific metadata typeClear boundary, deploy-safe by designMore objects to maintain
Record-level package.xml exclusionPrecise control per deploymentManual upkeep, error-prone at scale
Automated dependency-aware toolingConsistent, repeatable, no manual stepRequires a deployment platform that supports it

Protected Custom Metadata and Managed Package Traps

If you're working with managed packages, custom metadata types add another layer. Package developers can mark custom metadata types as protected, meaning subscriber orgs can't edit or query the records through the API at all. That's by design, meant to keep package configuration locked down. But it also means your deployment tooling can't touch those records, and any attempt to include them in a package.xml will throw an error rather than a warning.

Visible (unprotected) custom metadata types in managed packages behave differently again. Subscribers can add their own records, and those records need to survive package upgrades without being wiped out. This is a common source of confusion during CPQ and other managed-package-heavy deployments, where admins assume all metadata records are fair game and then watch a package upgrade silently strip out custom entries that weren't protected the way they expected.

The practical takeaway: before you build a deployment process around a managed package's custom metadata types, check the protection level on every object. It changes what you can automate and what has to stay a manual, documented step in your release process.

Building a Salesforce Custom Metadata Type Deployment Process That Doesn't Break Things

Reliable custom metadata deployments come down to three habits. First, treat every custom metadata object like source code: version it, review changes before they merge, and know exactly which records changed in a given release. Second, tag environment-specific fields clearly, either through a naming convention or a separate object, so nobody has to guess which values are safe to overwrite. Third, validate deployments against a real target org before they go live, not just against a syntax check.

Manual package.xml editing gets you through the first few deployments. It stops scaling once you've got a dozen custom metadata types with overlapping dependencies, some protected, some not, some carrying environment-specific fields that need to survive every release untouched. At that point the risk isn't whether a deploy will fail, it's whether it'll succeed while quietly breaking something nobody was watching.

This is the exact problem DeployEzee was built to handle. It maps metadata dependencies automatically, including custom metadata records and their relationships to fields, layouts, and other components, so you can see what's actually going to move before you click deploy. It also lets you flag environment-specific records so they're excluded from every future package without anyone having to remember a manual step. One-click deployment only works if the click knows what it's clicking on — for custom metadata types, that means understanding which records are configuration and which ones are landmines.

Sandbox testing catches a lot of problems. It doesn't catch the ones that only exist because production has a different endpoint value than your sandbox does. That gap is exactly where custom metadata deployments go wrong, and it's exactly where a dependency-aware deployment tool earns its place in the pipeline.

Frequently Asked Questions

Do custom metadata type records deploy automatically with the object?

No. Deploying the custom metadata type object definition does not automatically move its records. Records must be listed individually in package.xml using the CustomMetadata component type, formatted as ObjectName.RecordName, or they will not be included in the deployment.

Can I exclude specific custom metadata records from a deployment?

Yes, by omitting that record's entry from package.xml while including others of the same type. This gives you record-level control, but it has to be maintained manually unless your deployment tool tracks these exclusions automatically.

What happens if I deploy a custom metadata record that already has different values in the target org?

The deployment overwrites every field on that record with the source org's values. There is no field-level merge, so any environment-specific data on that record, like an API endpoint or org ID, gets replaced silently.

Are protected custom metadata types in managed packages deployable through the standard Metadata API?

No. Protected custom metadata types cannot be queried or edited by subscriber orgs through the API, which means standard deployment tooling cannot modify or include those records. Only the package publisher can change protected metadata records.

What's the best way to handle environment-specific values in custom metadata types?

Separate configuration that should stay identical across orgs from configuration that varies by environment, ideally into distinct custom metadata types. This makes it clear which records are safe to include in every deployment and which should be set once per org and excluded going forward.