Tag: llm

  • Get Your Data Right to Get Your AI Right

    Hey folks, Abhi here!

    I was listening to Marc Benioff this week, and one line stuck with me: “You’ve got to get your data right to get your AI right.”

    Honestly? That might be the most admin thing a CEO has ever said.

    Because here’s what I keep seeing. Teams get excited about Agentforce, spin up an agent in a sandbox, demo it to leadership, and then… it gives weird answers in production. Everyone blames the prompt. Or the model. Or “AI just isn’t ready yet.”

    Nine times out of ten, it’s none of those. It’s the data. The duplicate Accounts, the picklist with “Other”, “other” and “OTHER”, the custom field nobody has touched since 2019.

    So in this post, I want to break down what Benioff actually meant, why it lands squarely on us admins, and give you a practical checklist you can start on this week. No fluff. Let’s go.

    The four layers Benioff talked about

    In the clip, Benioff breaks enterprise AI into four connected layers. Here’s how I’d translate each one into admin language:

    1. Data is the foundation. Your records, fields, picklists, files. If this layer is messy, everything above it inherits the mess.
    2. Applications and semantics give the data business meaning. This is where “Status = Closed Won” actually means something. Think your data model, field definitions, validation rules, and Flows.
    3. Agents do the work. Agentforce reads from the two layers below and takes action.
    4. The interface is where people meet it all: Lightning pages, Slack, the agent chat window.

    Notice where agents sit. Third. Not first. They stand on top of two layers that we, the admins, have been building and maintaining for years.

    That’s the whole point. Building more agents is easy now. Making them useful, in a system the business can actually trust, is the hard part. And most of that hard part lives in layers one and two.

    Why agents trip over messy data

    Here’s the mental model I use: an agent reads your org the same way a brand-new hire does. Except it never asks “hey, which of these three Accounts is the real one?” It just picks one and answers with full confidence.

    When a request comes in, Agentforce pulls context from whatever its running user can see: CRM records, Knowledge articles, prompt template merge fields, and Data 360 objects if you’ve wired them up. Then the model reasons over whatever came back. There’s no step in that pipeline that asks “is this data actually good?” Retrieval treats your org as the truth.

    So a few classic admin headaches turn into real AI problems:

    • Duplicates. Three Account records for one customer? The agent might summarise them like three different customers.
    • Stale fields. This one is sneaky. An empty field gets skipped. A stale field gets quoted. If Renewal_Risk__c was last touched in 2024, the agent repeats it like it’s fresh news.
    • Mystery fields. Two fields holding the same fact, and nobody remembers which one is “real”? The agent doesn’t know either.
    • Old Knowledge articles. Your 2023 refund policy isn’t history to an agent. It’s an answer.
    • Access gaps. If the running user can’t see some records, the agent quietly answers from a partial picture. No error. Just wrong.

    And this isn’t just me being dramatic. Salesforce’s own 2026 State of Data and Analytics research found that data leaders estimate roughly a quarter of their organisation’s data is untrustworthy. Gartner has also forecast that through 2026, organisations will abandon 60% of AI projects that aren’t backed by AI-ready data.

    The worst part? A broken Flow throws an error you can screenshot. An agent on bad data fails in fluent, confident English. Users catch it once, start double-checking everything, and then quietly stop using it.

    Your admin checklist before the next agent

    Good news: none of this needs a new license. Just reports, SOQL, Setup, and an honest afternoon. And don’t try to clean the whole org. Scope it to the objects, fields and articles your agent will actually touch.

    • Turn on matching and duplicate rules for the agent’s core objects. Run the Duplicate Record Sets report, pick a survivor record, and merge the rest. Then keep the rules switched on so the mess doesn’t grow back.
    • List every field the agent reads. Go through its topics, actions and prompt templates. For each field, check how fresh the values are with a quick report grouped by Last Modified Date.
    • Kill or fix stale fields. If nobody maintains a field anymore, either fix the process that should update it or remove it from the agent’s grounding. Stale is worse than empty.
    • Clean up picklists. Merge “Other”, “other” and “OTHER” into one value, deactivate retired values, and use restricted picklists so new junk can’t sneak in.
    • Pick one source of truth per fact. Two fields storing the same thing? Choose one, give it an owner, and deprecate the other.
    • Review Knowledge articles in scope. Archive the dead ones, update the old ones, and resolve any that contradict each other.
    • Log in as the agent’s running user. Check permission sets, object access, field-level security and sharing. It should see everything the use case needs, and nothing beyond it.
    • Test with your ugliest records. Don’t demo on clean sample data. Use the Account that just survived a merge, or the customer with the weird history. If the agent handles those, it’ll handle the rest.

    If you only have time for three, do duplicates, stale fields, and the running user check. Those three alone fix most of the “why is the agent saying that?” moments I’ve seen.

    The “semantics” layer: why descriptions suddenly matter

    Remember layer two? This is the one admins tend to underrate.

    For years, field descriptions and help text felt optional. A nice-to-have for documentation day that never came. With agents, they’re context. When an agent sees a field called Status_2__c with no description, it’s guessing. When it sees a clear description of what the field means and when it’s used, it can reason properly.

    Salesforce is leaning hard into this too. Data Cloud was renamed Data 360 at Dreamforce 2025, and it’s now positioned as the data layer that gives Agentforce its context. On top of it sits Tableau Semantics, Salesforce’s semantic layer, which uses the Customer 360 Semantic Data Model to keep metric definitions consistent across clouds.

    The idea, as Salesforce’s architecture team explains it, is simple: define the business logic once, in a governed place, instead of letting every agent and dashboard guess which columns mean “churn risk”. Then agents and human analysts read the exact same definition.

    You don’t need Data 360 to start thinking this way, though. Inside your core org, the admin version of a semantic layer looks like:

    • Field descriptions and help text on every field the agent touches. Write them like you’re explaining to a new hire.
    • Clear, consistent labels and API names. No more Status, Status_New and Status_v2.
    • Specific topic and action instructions in Agentforce. Instead of “check order status”, name the exact field and object to look at.
    • Documented relationships. Make sure lookups and parent Accounts actually reflect how the business works.

    It’s boring work. It’s also the work that turns a cool demo into something people trust on a Monday morning.

    Wrapping up

    Here’s my honest take. Agentforce isn’t magic, and it isn’t broken either. It’s a mirror. It reflects whatever state your org is in, just faster and with more confidence.

    That’s actually great news for admins. The skills that make agents work are the skills we already have: data quality, clean data models, good security, clear documentation. The stuff we’ve been asking for budget for since forever just became the most important part of the AI story.

    So here’s my challenge for this week. Pick one process your team wants an agent for. Spend one afternoon running the checklist above against only that process. Write down what you find. Either you’ll go into the pilot with real confidence, or you’ll have found the actual project before it burned a quarter.

    And I want to hear from you: what’s the messiest part of your org’s data? Duplicates? Mystery fields? A Knowledge base from 2019? Drop it in the comments. I read every one.

    Until next time, keep it clean, folks! 🚀

    Sources

  • Named Query API: Give Your AI Agents Data Access Without Writing a Single Line of Apex

    Hey folks, Abhi here!

    I’ve been spending a lot of time with Salesforce Hosted MCP Servers and Agentforce lately. And the one feature that genuinely made me go “oh, that’s clever” wasn’t a fancy agent or a big AI model. It was a humble little thing called Named Query API.

    If you’re an admin who’s been wondering how to let AI agents read your Salesforce data safely, without handing them the keys to the entire org and without begging a developer for an Apex class, this one’s for you.

    So what is a Named Query?

    Simple version: it’s a SOQL query that you save in Setup, give a name, and Salesforce turns it into a reusable API.

    That’s it. You define SOQL queries from which you can then create custom agent actions for agents, and expose them as REST APIs for use by external applications. Write the query once, use it everywhere.

    The magic is in the parameters. Instead of hardcoding a value, you use a colon bind like :accountname. Whoever calls the query (an integration, an Agentforce agent, or an MCP client like Claude) only supplies that value. They can’t change the query itself.

    Why should admins care?

    Think about how we’ve traditionally given agents access to data. For Agentforce agents, data access is typically handled through tools such as autolaunched flows or Apex classes. With an autolaunched flow, you can use a Get Records element to retrieve data and return it as flow output.

    Nothing wrong with that. But building a whole autolaunched flow just to fetch a record and its related list? That’s a lot of clicks for a read operation. And the Apex route means a developer, a test class, and a deployment.

    With Named Query, all you need to do is to write the SOQL query using a dedicated UI instead of creating a flow or writing an Apex class.

    The other big win is control. If you give an AI agent a generic “run any SOQL” tool, it’ll happily write its own queries across whatever objects the user can see. That’s flexible, but unpredictable. A Named Query is the opposite: fixed objects, fixed fields, fixed filters. You decide exactly what the agent gets to see, and you test it once.

    An example: Account 360 in one call

    Say your sales team keeps asking an agent things like “give me a quick summary of Acme Corp.” To answer well, the agent needs the Account, its key Contacts, and its open Opportunities.

    Here’s a Named Query called AccountSummary that does exactly that:

    sql

    SELECT Id, Name, Industry, Type, AnnualRevenue, Owner.Name,
    (SELECT Id, Name, Title, Email FROM Contacts LIMIT 5),
    (SELECT Id, Name, StageName, Amount, CloseDate
    FROM Opportunities WHERE IsClosed = false)
    FROM Account
    WHERE Name = :accountname
    LIMIT 1

    See those two subqueries? That’s the part I love. One call returns the Account, its top Contacts, and its open pipeline together. The agent doesn’t need to make three separate lookups and stitch things together, and it gets clean, complete, related data in one response. That’s exactly what it needs to give a useful answer instead of a half-baked one.

    How to create one (step by step)

    Here’s the flow in Setup:

    1. Search for Named Query API in Setup and click New.
    2. Give it a label and API name. Heads up: the API name must begin with a letter and can contain only alpha-numeric characters, so no underscores.
    3. Enter the API version.
    4. Write a really good description. I’ll come back to why this matters so much.
    5. Paste your SOQL with your :parameter bind in the WHERE clause.
    6. Click Validate. The API endpoint is automatically created and displayed, something like /services/data/v65.0/named/query/AccountSummary?accountname=<name>.
    7. Add a description for each parameter.
    8. Save.

    If you want to use it as an Agentforce action, there’s an extra step. On the User Interface page, select Enable Salesforce Platform REST API, Named Query for Agent Actions (Beta). After you enable the beta and create a Named Query API, it’s automatically displayed in Setup > API Catalog. Manually activate Named Query APIs in the API Catalog to make them available as agent actions.

    Note that the agent action piece is still labeled beta in the docs, so check your org’s current release notes before building anything production-critical on it.

    Plugging it into an MCP server

    This is where it gets fun for anyone experimenting with Claude, ChatGPT, or other AI clients. Salesforce Hosted MCP Servers let you build custom servers, and custom tools let you expose Apex Invocable Actions, @AuraEnabled methods, Apex REST methods, Flows, and Named Queries as MCP tools without writing connector code.

    You build a custom server in Setup, add your Named Query as a tool, activate the server, and connect your AI client to the server URL. From there, the AI client sees your query as a tool it can call, complete with the description and parameters you wrote.

    Security: the part admins will love

    Named Queries don’t bypass your security model. With hosted MCP, your existing permissions automatically apply — CRUD, FLS, sharing rules, and all the other controls you’ve already mastered. Every transaction runs as the authenticated user, without anonymous service accounts or a new security model to learn.

    For Agentforce, the same idea applies: you’ll need to ensure that the user for whom the agent will invoke the Named Query API is authorized with read access to the object being queried.

    So if a sales rep can’t see the AnnualRevenue field, the agent working on their behalf can’t either. No new permission model to learn. Your profiles, permission sets, and sharing rules just keep doing their job.

    Gotchas to watch for

    1. Descriptions are your prompt. This is the big one. The AI decides when to use your query based on its description. The docs say it directly: if you intend to use the Named Query API as an agent action, AI agents will use it to understand when it’s optimal to select this action. A lazy description like “gets account” won’t cut it. Write something like “Returns an Account’s summary by name, including industry, owner, top contacts, and open opportunities.”

    2. Parameters only work in simple places. In a WHERE clause, use parameters in simple expressions such as name = :name, and with the logical operators AND, OR, NOT. Things like IN expressions don’t support parameters, so plan your filters accordingly.

    3. Limiting results has a naming rule. If a LIMIT clause in the top-level query has a colon parameter, it must be named maxrecords. Easy to miss, annoying to debug.

    4. Watch out for name-based lookups. Filtering on Name works great in a demo, but in real life? Two Accounts called “Acme” returns ambiguous results. A user typing “Acme Inc” when the record says “Acme Corporation” returns nothing. Your agent needs to handle both cases gracefully, ideally by asking for something unique instead of guessing. Consider a second Named Query keyed on a unique field like an external ID or Account Number, and describe both clearly so the agent picks the right one.

    5. Always add a LIMIT. Don’t let an agent accidentally pull back thousands of records. Keep your queries tight, including inside subqueries.

    When to use Named Query vs Flow vs Apex

    My rule of thumb:

    Named Query when you just need to read data, especially a record with its related lists. Fast to build, easy to maintain, no code.

    Flow when there’s logic involved: decisions, calculations, or you need to create or update records.

    Apex when you need complex transformations, callouts, or heavy processing that neither of the above can handle.

    Think of Named Query as your clean, safe read layer. It won’t replace Flow or Apex, but it’ll take a big chunk of simple “go fetch this for me” work off their plate.

    Wrapping up

    Named Query API is one of those features that sounds small until you use it. For admins, it means you can give AI agents and integrations precise, secure, read-only access to exactly the data they need, with nothing more than SOQL and a good description.

    Try building one in your dev org this week. Start with something simple like the Account summary above and wire it up to Agentforce or a hosted MCP server. You’ll be surprised how fast it goes.

    Got questions or a cool use case? Drop it in the comments. Happy querying!

  • Headless 360 Is Headless… Until Something Breaks

    Hey folks, Abhi here!

    I’ve spent the last few weeks testing Salesforce Headless 360 hands-on: connecting Claude to my org through a custom MCP server and letting the agent call my own automation as a tool. When it works, it feels like magic. When it doesn’t, you get one very unhelpful sentence and a trip back into Setup.

    In this post I’ll cover what I tested, what broke, how I debugged it, and what I think has to evolve before Headless 360 is ready for real-world agent work. I’ll also share what you can do today so your agents aren’t flying blind.

    Let’s get into it.

    First, a Quick Refresher on Headless 360

    If you haven’t been following along, Headless 360 is Salesforce’s big push to make the platform usable without the UI. Salesforce made Hosted MCP Servers generally available in April, and followed up with the Headless 360 MCP Server in Beta in early July.

    MCP (Model Context Protocol) is the open standard that lets AI clients like Claude, ChatGPT or Cursor call tools in other systems. With Hosted MCP Servers, Salesforce handles the hosting, authentication and permission enforcement for you.

    The part admins should care about most is custom MCP servers. You can expose Apex Invocable Actions, @AuraEnabled methods, Apex REST methods, Flows and Named Queries as MCP tools without writing connector code. Your existing automation can become an agent tool with a bit of configuration in Setup.

    Things have also moved quickly since spring. Salesforce has since expanded Headless 360 with a Data 360 MCP Server, a Slackbot MCP Client, more than 100 reusable Agent Skills, and capabilities across Marketing, Sales, Service, Commerce and more.

    So the vision is clear: agents do the work, and Salesforce provides the trusted business logic underneath.

    What I Tested

    My setup was simple:

    • A custom MCP server in my org, exposing one custom tool
    • The tool takes a handful of inputs, creates a couple of records and returns two IDs as outputs
    • Claude as the MCP client, connected through OAuth

    I gave Claude the inputs in plain English, and it mapped them to the tool’s input schema and called it. On a successful run, I got both IDs back within seconds.

    That’s the dream scenario: no clicking through tabs, no screen flows, just a conversation.

    What Happened When It Failed

    The first two attempts didn’t succeed. Here’s everything the agent got back:

    UNKNOWN_EXCEPTION
    An error occurred when executing...

    It didn’t include:

    • Which step failed
    • The actual error message
    • Any field-level detail
    • Anything about a validation rule, a duplicate rule, a missing field or a null value

    The agent had nothing to work with. It couldn’t tell whether my inputs were wrong, whether I lacked a permission or whether the automation itself was broken. All it could do was tell me something failed and suggest places to look.

    This is the core problem. An agent can only reason about what it can see. When a human hits an error in the UI, at least there’s a toast message or an error screen to go on. Here the agent got one generic line, which is less information than a human would see.

    The Debugging Detour

    So, back into Salesforce. Here’s the checklist I worked through, and it’s worth bookmarking if you’re building MCP tools.

    Step 1: Figure Out Who the Call Runs As

    My first question was whether this ran as an integration user or as me. For Hosted MCP, the answer is clear: every transaction runs as the authenticated user, scoped through an External Client App with the mcp_api scope, and CRUD, FLS, sharing rules, profile permissions and permission sets all apply.

    In my case, that meant my own System Administrator user, which ruled out most permission issues right away.

    Admin tip: You can confirm the running user by querying ApexLog and checking the LogUser field. The Operation field shows the exact action endpoint that was invoked, so you can match logs to tool calls.

    Step 2: Set a Trace Flag

    Go to Setup → Debug Logs and add a trace flag for the user who authenticated the MCP connection. Without one, you won’t capture anything.

    Step 3: Raise the Debug Level

    This one caught me. My first logs were tiny, around 1.5 KB for the whole transaction, because the default debug level barely logs declarative automation details.

    Set the Workflow category to FINER, with Apex Code and Database at INFO or higher. After that change, the logs showed element-level detail.

    Step 4: Read the Log

    Search the log for error events. The line just before the error usually names the element that failed and the real underlying message. That’s the context the agent should have received in the first place.

    A Twist

    After I raised the debug levels and retried with identical inputs, the call succeeded. That makes it hard to say exactly what went wrong in the earlier runs, and it shows why capturing the real error at the moment of failure matters so much. If the tool response had carried the detail, I’d have my root cause instead of a guess.

    Why This Matters More for Agents Than for Humans

    You might think this is just normal Salesforce debugging, and you’d be right. Admins have been reading debug logs for years.

    The context has changed, though. Headless means the person using the tool may never open Salesforce. It could be a sales rep in Claude, a support agent in Slack, or another agent in an automated chain. When something fails:

    • The end user can’t debug it. They don’t have access to Setup and shouldn’t need it.
    • The agent can’t recover. It doesn’t know whether to retry, fix an input or stop.
    • The admin gets pulled in for issues the agent could have handled with a clear error.

    Salesforce’s own pitch for the Headless 360 MCP Server is that agents understand the relationships, permissions, workflows, validation rules and governance behind how the business operates. I love that. But if the agent understands validation rules on the way in, it should also hear about them on the way out.

    What Needs to Evolve

    Here’s my wishlist for the Salesforce product team:

    1. Return the real fault message in the tool response. If a validation rule blocks the save, pass along the validation rule’s error message. If a required field is missing, name the field.
    2. Identify the failing step. Even just the element or method name would cut debugging time dramatically.
    3. Keep error codes specific. UNKNOWN_EXCEPTION should be a last resort, not the default.
    4. Give agents safe, scoped access to execution context, so a user with the right permissions doesn’t need a human to set trace flags and dig through logs.
    5. Add observability for MCP calls in Setup. A simple view of recent tool invocations, their inputs, outcomes and errors would be a big help.

    Headless only really works if debugging works without the browser too.

    What You Can Do Today

    You don’t have to wait for Salesforce. Here’s how I’m building MCP tools now:

    • Handle faults explicitly. Catch errors in your automation rather than letting them bubble up unhandled. An unhandled fault is what produces the generic exception.
    • Return errors as outputs. Add output variables like isSuccess and errorMessage, and populate them in your fault handling. The agent then gets a readable error and can act on it.
    • Write helpful validation rule messages. “Phone must include country code, e.g. +91” gives an agent something to fix. “Invalid value” doesn’t.
    • Describe your tools well. When an MCP client lists tools, the agent sees only a name, a description, an input schema and an output schema. Spell out input formats in the description, such as dates as YYYY-MM-DD and phone numbers with country code.
    • Keep a debug checklist ready: the running user, a trace flag, Workflow at FINER, and a quick ApexLog query.
    • Test in a sandbox first. Custom MCP servers can be deployed via Metadata API between sandboxes and production, so treat them like any other metadata and test before you promote.

    Wrapping Up

    I’m genuinely excited about Headless 360. Calling trusted Salesforce logic from any AI client, with permissions enforced for you, is a big shift for how our users will work.

    But agents are only as good as the feedback they get. Right now, when a custom tool fails, the agent is mostly in the dark and an admin ends up in Setup reading logs. For headless to be production-ready, error handling has to evolve along with everything else.

    Until then, build your tools defensively, return meaningful errors and keep your debug checklist handy.

    Have you tried Headless 360 or custom MCP servers yet? Hit the same wall? Let me know in the comments. I’d love to hear how you’re handling it.

    Happy building! 🚀

  • MCP Servers, Explained in 8 Steps

    Hey folks, Abhi here!

    Quick one today — let’s demystify “MCP Server” with a simple example, because I keep seeing people throw the term around without explaining what actually happens under the hood.

    The example

    Say you type this into Claude:

    “Show me open cases for Acme”

    Claude doesn’t have your Salesforce data memorized. So here’s what happens in real time, step by step:

    1. You ask the question in plain English.
    2. The LLM inside Claude reads it and realizes — “I need a tool for this, I can’t answer from memory.”
    3. Claude’s MCP Client (a small piece living inside the app) packages that need into a JSON-RPC 2.0 message and sends it to the right MCP Server.
    4. The MCP Server turns that generic request into something Salesforce understands — think a SOQL query or an Apex REST call.
    5. Salesforce runs it and returns raw data.
    6. The MCP Server shapes that raw data into a clean, structured result.
    7. It travels back through the MCP Client, into Claude.
    8. The LLM writes the plain-English answer you see on screen — no CSV export, no manual lookup.

    Why this matters for admins

    That whole 8-step round trip happens in a second or two, every single time. The MCP Server is the translator — it’s the only piece that actually knows how to talk to Salesforce. The MCP Client is just a router. The LLM is the only part actually “thinking” — deciding a tool is needed, and later turning the result into words.

    This is the exact plumbing sitting under every “AI agent talks to Salesforce” demo you’ve seen lately, including Agentforce. If you’re evaluating a third-party MCP server for your org, this is also where you should be asking security questions — who built the server, what does it have access to, and how is that JSON-RPC channel authenticated.

    That’s it — MCP in one example. I’m diving into Headless 360 next, so more on that soon. Catch you in the next one!

  • Stop Describing Your Whole Org to Find One Object: getGlobalDescribe() vs describeSObjects() vs Type.forName()

    Hey folks, Abhi here!

    Quick question to start: how many times have you written this line without thinking twice?

    apex

    Schema.SObjectType t = Schema.getGlobalDescribe().get(objectName);

    Be honest. It’s muscle memory at this point. It works, your tests pass, you move on with your life. But here’s the thing — that one harmless-looking line is often the most expensive way to do the thing you actually wanted to do. And if your code is heading anywhere near an AppExchange security review, one of these patterns will get you flagged.

    So let’s break down the three usual suspects for resolving and describing an SObject dynamically — what each one actually costs, when to reach for it, and when to keep your hand in your pocket. No fluff, just the stuff that’ll make your Apex leaner and your security reviewer happier.

    The contenders, at a glance

    Before we dig in, here’s the lineup:

    • Schema.getGlobalDescribe() — hands you a Map<String, SObjectType> of every object in the org. Describes everything. Doesn’t resolve Apex classes.
    • Schema.describeSObjects(List<String>) — describes only the names you pass in. Scoped to exactly what you asked for. Doesn’t resolve Apex classes.
    • Type.forName(name) — returns a System.Type you can call newInstance() on. A single type lookup. And yes — it resolves Apex class names too. (Hold that thought.)
    • The forgotten one: Account.SObjectType — the static token. Zero lookup cost, resolved at compile time. Most people forget it exists.

    Now let’s actually use them.

    1. getGlobalDescribe() — the sledgehammer

    getGlobalDescribe() builds a map of all objects in your org. Standard, custom, and every single object dragged in by every managed package you’ve ever installed.

    apex

    // You wanted ONE object. You just described thousands.
    SObjectType t = Schema.getGlobalDescribe().get('Account');

    Here’s the problem: the cost scales with the size of your org, not with your need. In a clean dev org, this feels free — you’ll never notice it. But take that same line into a mature enterprise org running 30 managed packages, and that map can hold thousands of entries. That’s real CPU time to build it and real heap to hold it.

    Now, to be fair — describes are cached per transaction, so it’s the first call that’s expensive, not every call. But you’ve still paid for describing thousands of objects you’re never going to touch. That’s the part that stings.

    Use it when: you genuinely need to enumerate or iterate every object — a metadata explorer, a generic admin tool, a “list all objects” picker. When “everything” is literally what you need, this is the right tool.

    Don’t use it when: you already know the object’s name. Which, let’s be real, is the 95% case.

    2. describeSObjects() — the scalpel

    apex

    SObjectType t = Schema.describeSObjects(
    new List<String>{ 'Account' }
    )[0].getSObjectType();

    This describes only the objects you name. One object, one describe. Need a handful? Pass them all in one call:

    apex

    List<DescribeSObjectResult> results = Schema.describeSObjects(
    new List<String>{ 'Account', 'Contact', 'Opportunity' }
    );

    The cost here is proportional to what you actually asked for — independent of how bloated your org is. That’s the whole point.

    One gotcha: it throws if you pass an invalid name. So when the input comes from a user, config, or a payload, wrap it:

    apex

    private static SObjectType resolveSObjectType(String objectName) {
    if (String.isBlank(objectName)) {
    throw new AuraHandledException('Object name is required.');
    }
    try {
    return Schema.describeSObjects(
    new List<String>{ objectName }
    )[0].getSObjectType();
    } catch (Exception e) {
    throw new AuraHandledException(
    'Object "' + objectName + '" not found or not accessible.'
    );
    }
    }

    Bonus tip — lazy loading: there’s an overload, describeSObjects(types, SObjectDescribeOptions.DEFERRED), that defers loading the expensive child-relationship metadata until you actually ask for it. If you only need field info and not relationships, this trims the cost even further. Most people don’t know it’s there.

    Use it when: the object name is dynamic — from config, a payload, a UI input — and you only want that object’s metadata. Honestly, this should be your default for dynamic describe.

    3. Type.forName() — the wrong tool for SObjects (usually)

    You can resolve an SObject type this way:

    apex

    SObjectType t = ((SObject) Type.forName('Account').newInstance()).getSObjectType();

    But look closely at what just happened: you instantiated an object just to ask it what type it is. That’s already weird. But it gets worse, because Type.forName doesn’t only resolve SObjects — it resolves Apex class names too. Feed it user-controlled input and .newInstance() will happily run the no-arg constructor of whatever class matches that string.

    That’s two problems stacked on top of each other:

    • Performance: you built an instance you never needed.
    • Security: instantiating arbitrary types from user-controlled input is exactly the pattern an AppExchange security reviewer flags. And relying on the (SObject) cast to fail afterward? Too late — the constructor already ran.

    So when should you use it? When you’re doing genuine dynamic Apex — a factory pattern, custom-metadata-driven dispatch, plugin architecture. Stuff where instantiating a class by name is the actual goal:

    apex

    // Legitimate use: build a handler chosen by config — not an SObject lookup
    MyInterface handler = (MyInterface) Type.forName(config.ApexClassName__c).newInstance();

    Don’t use it when: you just want an SObjectType or a describe. Use describeSObjects instead — it physically can’t be tricked into running a constructor.

    The one everyone forgets: the static token

    Here’s the kicker. If you know the object at compile time, you don’t need to describe anything at all:

    apex

    SObjectType t = Account.SObjectType; // free
    DescribeSObjectResult d = Account.SObjectType.getDescribe();

    Zero lookup cost. Whenever the object is hard-coded, reach for this first. No map, no list, no instantiation. It’s the cleanest option and it’s sitting right there.

    A quick word on governor limits

    You might be thinking, “Abhi, describes don’t even count against my SOQL limit, so who cares?” Fair point — describe calls don’t consume SOQL queries, and modern API versions removed the old hard cap on the number of describes you can run.

    But “no hard limit” doesn’t mean “free.” The real ceilings here are CPU time and heap size — and that’s precisely where getGlobalDescribe() quietly eats your transaction alive in a big org. And remember, describes are cached per transaction, so the cost is front-loaded on first access, not spread out per call. Plan accordingly.

    TL;DR — which one, when

    • Object known at compile time → Account.SObjectType
    • Dynamic name, need one (or a few) objects → Schema.describeSObjects(List<String>)
    • You truly need to iterate every object → Schema.getGlobalDescribe()
    • Instantiating an Apex class by name → Type.forName(...).newInstance()
    • Resolving an SObject type from user input → not Type.forName — use describeSObjects

    The rule of thumb is simple: describe exactly what you need, no more. Reach for getGlobalDescribe() only when “everything” is what you need, and keep Type.forName for dynamic class loading — never as a backdoor SObject resolver, especially in code headed for security review.

    So next time your fingers start typing getGlobalDescribe().get(...) out of habit — pause. Ask yourself: do I actually need the whole org? Most of the time, the answer’s no. And swapping that one line for describeSObjects cuts the per-call cost, sidesteps the Type.forName security smell, and sails a lot cleaner through any AppExchange-readiness checklist. Same line of code, massively better behavior.

    Catch you in the next one!

  • Salesforce Summer ’26 for Developers: The Technical Deep Dive

    Hey devs, Abhi back again!

    After my admin-focused Summer ’26 post, a bunch of you reached out asking, “Bro, what about us developers?” Fair point. So here’s the technical companion — all the Apex, LWC, and platform changes that are going to land in your code review queue over the next few weeks.

    Quick heads up: this release has the most impactful Apex security change in years. If you’re upgrading code to API version 67.0, you genuinely need to read the first section carefully. Let’s get into it.

    🚨 The Big One: API Version 67.0 and Apex User Mode

    This is the change everyone’s talking about, and rightly so. Salesforce is finally moving Apex toward secure-by-default behavior. Here’s what shifts when you compile a class at API version 67.0 or later:

    1. Database Operations Run in User Mode by Default

    Previously, SOQL, SOSL, DML, and Database methods ran in system mode — meaning they ignored object permissions, field-level security (FLS), and sharing rules. In v67, the same code now runs in user mode by default.

    So this query:

    apex

    List<Account> accounts = [SELECT Id, Name, Sensitive_Field__c FROM Account];

    In v66 → runs in system mode, returns everything regardless of user permissions. In v67 → runs in user mode, respects the running user’s FLS, object permissions, and sharing rules.

    This is a massive shift. If your code expects to see records or fields the running user doesn’t have access to, those queries may return fewer rows — or throw an exception — where they previously worked fine.

    You can still explicitly opt into system mode when needed:

    apex

    List<Account> accounts = [SELECT Id, Name FROM Account WITH SYSTEM_MODE];

    Or for Database methods:

    apex

    Account acc = Database.query(
    'SELECT Id, Name FROM Account WHERE Rating = \'Hot\'',
    AccessLevel.SYSTEM_MODE
    );

    2. Classes Default to with sharing

    In API v66 and earlier, an Apex class without an explicit sharing declaration defaulted to without sharing (with some exceptions). From v67 onwards, the default is with sharing.

    apex

    // In v67+, this class enforces sharing rules by default
    public class AccountService {
    // No sharing declaration = with sharing now
    }

    My honest advice: always declare sharing explicitly. Don’t rely on defaults for security-critical code. It’s just better hygiene.

    3. WITH SECURITY_ENFORCED is Removed

    If your codebase still uses WITH SECURITY_ENFORCED, you need to replace it. Apex classes set to API v67.0+ that include this clause will not compile.

    Replace it with WITH USER_MODE:

    apex

    // Old (won't compile in v67+)
    SELECT Id, Name FROM Account WITH SECURITY_ENFORCED
    // New
    SELECT Id, Name FROM Account WITH USER_MODE

    WITH USER_MODE is actually better — it supports polymorphic fields and returns the full set of access errors, not just the first one.

    4. Triggers Always Run in System Mode

    Apex triggers now always run in system mode, regardless of API version. You can no longer declare explicit sharing or access modes on triggers. If you had logic depending on user-mode trigger behavior, move it into a handler class where you can control the context.

    What You Should Actually Do

    Old classes are unaffected unless you explicitly bump them to v67. So the danger isn’t automatic — it’s when you upgrade. Here’s my checklist:

    1. Audit your code for queries that rely on system-mode behavior. Service classes, utility libraries, and managed package code are usually the biggest offenders.
    2. Run your test classes with System.runAs() blocks. Without proper runAs, your tests probably don’t actually validate the user-mode boundary.
    3. Don’t bulk-upgrade everything to v67 on day one. Pick one class, test, deploy. Iterate.
    4. Plan for retirements too. Platform API versions 31.0 through 40.0 are deprecated and will fully retire in Summer ’28. If you have ancient integrations, now’s the time.

    Multiline Strings and String Templates in Apex

    Finally. Finally. This is the QoL upgrade I’ve been waiting years for.

    Multiline Strings

    Use triple single quotes to define a multiline string:

    apex

    String myPoem = '''
    Multiline strings are here, hooray!
    You can start using them today.
    Triple quote, then start a new line,
    For example this will work fine,
    Poetry is not my forte!
    ''';

    No more 'line one\n' + 'line two\n' + ... concatenation nightmares.

    String Templates

    Pair multiline strings with the new .template() method to do clean variable interpolation:

    apex

    String message = '''
    Hello ${firstName},
    Your order was dispatched on ${dispatchDate}.
    Thanks,
    The Team
    '''.template(new Map<String, Object>{
    'firstName' => 'Abhi',
    'dispatchDate' => Date.today()
    });

    Email bodies, JSON payloads, debug messages, HTTP request bodies, generated SOQL — everything that used to be a concatenation mess becomes readable.

    LWC State Management (Now GA)

    State Management for Lightning Web Components is officially GA in Summer ’26. If you’ve ever built two components on the same page that need to share state and ended up with a tangle of CustomEvent dispatches plus pubsub plus Lightning Message Service — this is for you.

    Picture an Opportunity page with two components: one listing line items with the ability to add new ones, and another showing the total amount. With State Management, both components can subscribe to a shared state store, and updates from one automatically reflect in the other — no event plumbing required.

    This is a fundamental architectural improvement for any team building complex multi-component pages.

    LWC Live Preview (GA)

    Previously called Lightning Preview, this is now Live Preview and it’s generally available. You can preview a single LWC in your browser or directly in Visual Studio Code without reloading the entire Lightning page.

    From the CLI:

    bash

    sf lightning dev component

    The CLI prompts you for whatever flags it needs. The preview tab updates automatically as you save changes. Single Component Live Preview supports Lightning Data Service wire adapters, @salesforce scoped modules, and Apex controllers.

    If you’ve ever waited 90 seconds for a Lightning page to reload just to verify a CSS tweak, this changes your life. I’m not exaggerating.

    Web Console (Beta) — The New Developer Console

    Salesforce is shipping a client-side IDE embedded directly in Salesforce. Think of it as the modern successor to the old Developer Console.

    What you can do in Web Console:

    • Build and run SOQL queries
    • Configure trace flags and debug log levels
    • Execute anonymous Apex
    • Build, debug, and deploy Apex
    • It opens automatically when you access Apex Classes, Apex Triggers, or Apex Jobs in Setup

    Enable it: Setup → Quick Find → “Development” → Web Console (Beta) → toggle on, then refresh.

    It’s not going to replace VS Code + Salesforce CLI for serious development, but it’s a great quick-fix tool and a much better learning surface for new developers who aren’t ready to set up a local toolchain.

    Release Manager Three-Channel System (Beta)

    This is a really thoughtful addition for teams that want to stay ahead of the release cycle. Salesforce introduced three channels:

    • Standard — The stable production release everyone’s on.
    • Accelerated — Production-ready features that haven’t been released in a major release yet.
    • Dev — New features in active development, often without full documentation.

    Features get promoted from Dev → Accelerated → Standard as they mature. Opting into the Dev channel is how you get access to things like:

    Dynamic Lists (Developer Preview)

    Two new components — lightning-dynamic-list-container and lightning-dynamic-list-item — that use virtualization to render large lists efficiently. Whether you’re displaying 50 or 5,000 items, the browser only renders what’s visible. No more pagination workarounds for big lists.

    To use, opt into the Dev channel in Salesforce Release Manager.

    LWC in Dashboards (GA)

    You can now embed custom Lightning Web Components directly as dashboard widgets. In dashboard edit mode, click +Widget and select Lightning Web Component, configure properties, and you’re done.

    This is huge for analytics use cases. Want a waterfall chart, custom drill-down behavior, or interactive filtering inline on a dashboard? Build it as an LWC and drop it in. No more separate Lightning pages.

    Apex Action Improvements for Flow

    For those building Apex actions to be consumed in Flow Builder, several InvocableActionExtension metadata enhancements just landed:

    • Custom property editors per input (not just for the entire action)
    • Picklists for action inputs — define valid values declaratively
    • Custom headers on the standard property editor
    • Metadata type assignments for inputs

    This is especially valuable for managed package developers. You can ship Flow actions with a guided, polished configuration UX that doesn’t require your customers to read documentation.

    You can also now provide custom styling hooks for your custom Flow Screen Components written in LWC — color, radius, weight, and other CSS attributes. Group them logically and customers get a tidy customization experience that matches their org’s branding.

    A Few Other Wins Worth Knowing

    • External Services support for enums — Include enums in OpenAPI specs and they appear as picklists in Flow Builder.
    • Hosted MCP Servers (GA) — Salesforce-hosted Model Context Protocol servers are now GA. Any MCP-compatible AI client can connect via OAuth.
    • Named Query API (GA) — Expose custom SOQL as scalable actions for REST API clients and AI agents.
    • Voice Toolkit API — Build voice-enabled LWC and Aura components for Service Cloud Voice.

    My Upgrade Strategy

    If I were running this in a real org, here’s how I’d sequence the work:

    1. Spin up a sandbox on the preview track and validate critical Apex against API v67.0.
    2. Identify high-risk classes — anything querying sensitive data, anything without explicit sharing declarations, anything using WITH SECURITY_ENFORCED.
    3. Don’t upgrade everything to v67 in one go. Bump non-critical classes first, test, then move to critical ones.
    4. Enable Web Console (Beta) in a sandbox — great for ad-hoc debugging.
    5. Try Live Preview in your local dev setup. It’ll change how you build LWCs.
    6. Refactor a multi-line string somewhere to feel the joy of triple-quotes.
    7. Evaluate State Management for any multi-component pages you maintain.

    Summer ’26 isn’t flashy on the developer side — but the Apex security changes alone make this one of the most consequential releases in the last few years. Plan the upgrade carefully, test thoroughly, and you’ll come out the other side with a more secure and maintainable codebase.

    What’s the change you’re most excited about? Or what’s the one that has you worried? Drop a comment — I’d love to hear what’s keeping you up at night with this release.

    Catch you in the next one!

    — Abhi