Salesforce, Python, SQL, & other ways to put your data where you need it

Need event music? 🎸

Live and recorded jazz, pop, and meditative music for your virtual conference / Zoom wedding / yoga class / private party with quality sound and a smooth technical experience

How I set up VSCode for a new Salesforce project

21 Dec 2021 🔖 salesforce vscode
💬 EN

Table of Contents

It seems my “Setting up VSCode to edit Salesforce metadata” article is still pretty popular, and although I haven’t followed it step-by-step in a while, at a glance I suspect it’s not particularly wrong, per se. However, it’s extremely oriented toward “org-driven development” in sandboxes.

As documented in “Random handy CumulusCI notes”, I’ve been starting to play with “source-driven development” in scratch orgs – like for real, not just for Trailhead points.

This article is my attempt to “think out loud” as I work through whether maybe there’s a good way to set up VSCode, my Salesforce CLI configuration, and my CumulusCI configuration so that folders on my hard drive feel uncluttered but also offer me the flexibility to play with both models of development.

Also, a commenter asked me 2 years ago, “How can I retrieve and deploy individual Flows from/to Production?” on the VSCode article.

While I’m hesitant to teach people how to build themselves buttons that make end-runs around change management practices developed with their colleagues, I feel like there is some level of “source-driven development” that’s really nice for bundling up related flows … but then gets really hard for actually deploying into sandboxes (and – ahem – production, I suppose) without good tooling.

Figuring out “the perfect tooling” and “the perfect way of organizing code” for small teams doing in-house development is a little beyond the scope of these notes, but it’s end goal I’m fumbling toward.

This article presumes you’ve already enabled dev hub features in relevant production or dev orgs.

TL;DR file tree for power devs

├── .cci
├── .ignoreme
├── .sfdx
│   ├── indexes
│   │   └── ... (let the VSCode SFDX auth process make this) ...
│   ├── tools
│   │   └── ... (let the VSCode SFDX auth process make this) ...
│   ├── typings
│   │   └── ... (let the VSCode SFDX auth process make this) ...
│   └── sfdx-config.json
├── .vscode
│   └── settings.json
├── force-app
│   └── main
│       └── default
├── orgs
│   ├── beta.json
│   ├── dev.json
│   ├── feature.json
│   └── release.json
├── .gitignore
├── cumulusci.yml
└── sfdx-project.json

Computer-wide setup

  1. I open a Windows command-line terminal by hitting the windows key to open the start menu, typing cmd, and hitting Enter.
  2. I execute the command cd c:\example – any random folder where I know I don’t normally keep Salesforce-related files will do.

SFDX and CCI installation validation

The first thing I do once I’ve got VSCode installed is validate that the Salesforce CLI and the CumulusCI CLI are also installed and are working correctly.

I won’t cover how to install everything, but for validation:

Then, from the command prompt that says c:\example>

  1. I execute the command:
     sfdx version
    • I’m looking for a response like sfdx-cli/#.#.# win32-x64 node-v#.#.#.
  2. I execute the command:
     cci version
    • I’m looking for a response like CumulusCI version: #.#.# ... See the latest CumulusCI Release Notes ....

SFDX and CCI general validation

  1. I validate that the Salesforce CLI is appropriately confused in a c:\example context by executing the command:
     sfdx force:org:display
    • It says, ERROR running force:org:display: This command requires a username. Specify it with the -u parameter or with the "sfdx config:set defaultusername=<username>" command.. Good.
  2. I validate that the CumulusCI CLI is appropriately confused in a c:\example context by executing the command:
     cci project info
    • It says, Error: The file cumulusci.yml was not found in the repo root: c:\myfolder. Are you in a CumulusCI Project directory?. Good.

SFDX global authentication and aliasing

I execute the command:

sfdx auth:list

(It’s a lot easier to read the results if you make the Windows command prompt full-screen.)

I poke through it, reading the usernames, instance urls, and if I’ve got a really good memory, org IDs, seeing if I’ve already told the Salesforce CLI about a given production, sandbox, or dev org in the past.

(I might see some scratch orgs in there, but I don’t care much about them – I always just manage scratch orgs with the CumulusCI CLI these days, not with the Salesforce CLI. However, I do want the Salesforce CLI to have aliases on hand for, and be logged into, any production or dev orgs that are serving as “dev hubs.”)

Noting an existing alias

If I see an org I know I’m going to be making my computer download code from, or upload code into, I write down its alias.

Let’s say it was named my-clever-alias.

I also like to run the following command to make sure that the Salesforce CLI is still logged into the org represented by `my-clever-alias:

sfdx force:org:display --targetusername my-clever-alias

I should see something like this:

WARNING: This command will expose sensitive information ...

=== Org Description
KEY               VALUE
Alias             my-clever-alias
Client Id         PlatformCLI
Connected Status  Connected

If Connected Status has a value like RefreshTokenAuthError instead of Connected for Connected Status, just follow the steps to create a new alias, but when you set the alias, be sure to set it to the same alias that was already in use.

Creating a new alias

If I don’t see my org in the list, I can log the Salesforce CLI into the org, giving it an alias of my-clever-alias by running this command (the --instanceurl might be just or for you if you don’t use a custom domain):

sfdx force:auth:web:login --setalias my-clever-alias --instanceurl

My web browser opened, and since this was my first time in a while connecting the Salesforce CLI to the org, the browser asked me “Allow Access?”, for which I clicked “Allow,” and then once I saw Salesforce as usual, I closed the browser tab and returned to the Windows command prompt.

I saw a message in the Windows command prompt like:

Successfully authorized [email protected] with org ID 00A123456789876543


  1. sfdx auth:list has my-clever-alias in it.
  2. sfdx force:org:display --targetusername my-clever-alias returns something like this:
     WARNING: This command will expose sensitive information ...
     === Org Description
     KEY               VALUE
     Alias             my-clever-alias
     Client Id         PlatformCLI
     Connected Status  Connected

CCI global authentication and aliasing

I execute the command:

cci org list

Shoot – this stopped working sometime between 12/21/21 when I wrote the article and 1/31/22 when I tried to follow my own previous steps.

Now when I run this command from outside of a CCI project, I get an error:

Error: 'NoneType' object has no attribute 'write_text'

I discovered this when trying to debug why CumulusCI suddenly (as in, within the last two weeks) seems to have stopped letting me jump between Git repositories on my computer, deploying code from both of them to a single scratch org with cci commands.

(It was a workaround for using a buggy GitHub repository as the foundation of a scratch org – it was buggy enough it couldn’t be included as a dependency by its GitHub URL, but I could create a scratch org based off of it, then switch over to the repo I was actually trying to make and push that code into it next … but now the second folder is trying to create a new dev scratch org, ignoring the fact that I just created one in another folder.)

It’s weird because I don’t remember upgrading CumulusCI, but scratch org state suddenly seems less “global” and more tightly scoped to folders.

I wonder what happened.

Anyway, this section might also not work for you until I figure this out.

There will probably be a table of Scratch Orgs with names like beta, dev, etc. but at this point I don’t really care about those.

All I care about that is whether there’s a table of Connected Orgs below it.

I don’t need to explicitly tell the CumulusCI CLI about production, sandbox, or dev orgs if their only function in my life is going to be that they serve as “dev hubs” to scratch orgs I plan to create. It suffices that I’ve told the Salesforce CLI about them.

However, if I’m going to be using CumulusCI CLI commands (not just Salesforce CLI commands) on my computer to download code from, or upload code into, one of these orgs, then I do need the CumulusCI to know about it as a “connected org.”

  • Sometimes, I do this because the entire purpose for the existence of a folder on my hard drive is as a backup of the way certain metadata for a production, sandbox, or dev org has changed over time, and for one reason or another, I’ve found myself needing to use CumulusCI CLI commands to download copies of the metadata instead of Salesforce CLI commands.
  • Other times, I do this because I need to grab little bits and pieces of code from a production, sandbox, or dev org and add them to a folder on my hard drive that makes up a more “org-agnostic” codebase. For example, if I were contributing an enhancement to NPSP, and we’d done something awesome at my company that I wanted to imitate, maybe I’d do a one-off download of a few of those “awesome bits” out of my company’s production org and into my computer’s local copy of the NPSP codebase.
    • A more realistic example of the same technical workflow? I’m developing a miniature “org-agnostic” codebase specific to a business problem at work. The Apex class or Flow I’m about to invent is going to need to refer to certain custom fields that I know already exist in our production org or one of our sandboxes. I need to download copies of those custom fields, and make them part of this codebase, if I want scratch orgs based on my mini-codebase to work without errors.
  • CumulusCI connected orgs are also handy when I’ve developed a miniature “org-agnostic” codebase specific to a business problem at work, and it works really well in scratch orgs, and now I want to see how well the changes I made perform in actual sandboxes.

If I don’t see my org in the list, I can log the CumulusCI CLI into the org, giving it an alias of my-clever-alias by running this command (the --login-url might be just or for you if you don’t use a custom domain):

cci org connect my-clever-alias --global-org --login-url

Or, for a sandbox:

cci org connect my-clever-alias --global-org --sandbox --login-url


  1. cci org list has my-clever-alias in it under the name column.

You don’t have to use the same alias name as you used for the org in the Salesforce CLI, but I recommend it to keep your life simpler.

Also, if you’re afraid you might make typos and upload code to “connected orgs” from codebases that should never end up in those orgs, don’t do this command with the --global-org bit from something like c:\example.

Instead, you’ll do it without --global-org from a folder on your computer where the actual project you’re working on lives – more on that later in this article.

If you already added the org globally, you can take it back out of the CumulusCI “global connected orgs” by executing the command:

cci org remove my-clever-alias --global-org

Exit the command prompt

To close the Windows command prompt, which I’m now done with, I execute the command exit to exit the Windows command prompt.

Project-specific setup

My first step, when it comes to actually setting up project-by-project codebase tracking, is always to open Windows Explorer and navigate to c:\git_repos\sf\, which is the folder where I keep all of the folders on my computer that represent Salesforce codebases whose changes I’d like to track with Git.

Folder creation

Once I have an idea for a codebase, I give it its own folder on my hard drive.

  1. Some codebases are miniature collections of a specific clump of business logic – like a handful of custom fields and some Record-Triggered Flows that act upon them. I’m always careful to make sure that the code in these is “org-agnostic” and works well in “neutral” orgs like dev orgs and scratch orgs, not just sandboxes.
    • So even if feel like my job is to “write a flow,” the codebase includes copies of the definitions of any custom fields that would cause my flow to error out if they didn’t exist in my org.
    • In other words, “org-agnostic” means more “batteries included – but just the bare minimum of batteries necessary,” as opposed to meaning, “this automation is so vanilla that it’s useless and has nothing to do with my company.”
    • That said, if I find myself needing to put the SAME set of “batteries” (e.g. a widely-used custom field like Contact.Foreign_Postal_Code__c) into more than one mini-clump of business logic, I put those in a separate folder, a copy of which is stored on GitHub, and I use CumulusCI to cross-reference it from the project I’m actually working on that needs “batteries,” much like I might warn CumulusCI to spin up all scratch orgs for a given project I’m working on with the NPSP pre-installed.
  2. Other codebases are thorough backups of a very specific production or sandbox org.

So if I work at a company with 3 production orgs (one for the IT team, another for a sales team, and a third for a service team), I might have 6 folders under c:\git_repos\sf\, and all of my project folders actually go into one of those:

  1. corp-it
  2. corp-sales
  3. corp-service
  4. misc-dev-orgs
  5. misc-open-source
  6. misc-multi-org

If I were starting up a folder for keeping track of “important changes” to the production “IT team” org, I might create a new folder at c:\git_repos\sf\corp-it\org-backups\prod.

On the other hand, if I were starting up a folder for a project that was going to automate Contact owner assignment, and which eventually was going to be installed into the production “sales team” org, but which I’d been told I should think about making a bit more “org-agnostic” so we could also install it into the “IT team” and “service team” orgs one day, like a managed package, I might create a new folder at c:\git_repos\sf\corp-it\misc-multi-org\projects\contact-owner-assign.

Or, if I knew it’d likely never be used for other orgs, but I just wanted to keep it feeling neatly organized, I might still design it in a rather “org-agnostic” manner but keep it on my hard drive under c:\git_repos\sf\corp-it\corp-sales\projects\contact-owner-assign.

This is definitely an art, not a science!

And if each of these folders on your hard drive is going to be tracked independently of every other folder with Git, it really doesn’t matter to anyone else at your company how you name the folders, if it works for you.

It will, however, matter what project names you give the “remote” copies of these folders’ contents when you store them on a web site like GitHub. You won’t get the benefit of a hierarchichal nested folder structure to work with on such sites, so name things carefully with your team when you’re storing things in a shared space like GitHub.

For the rest of this tutorial, I’m going to use c:\myfolder to represent the directory I just created because it’s shorter for you to read.

Folder tracking with Git

A lot of Salesforce CLI and CumulusCI CLI commands work better when you’re tracking changes to the contents of important folders on your hard drive using software called Git.

There are a lot of ways to tell Git that you’d like to track the folder you just created, but personally, I navigate into my new folder in Windows Explorer, right-click in the white space, and click “Git Bash Here.” If I forgot to install Git in a way that offers this option in my Windows Explorer context menus, I open Git Bash from the Windows start menu and type: sh cd /c/git_repos/sf/it/prod

  • To start tracking this folder with Git, I execute the command:
      git init
  • Optional steps I like to do, as a more experienced Git user:
    • Back in Windows Explorer, I click the View tab and, under the Show/hide section, make sure Hidden items is checked. I navigate into the .git folder that was just created and open the file config with a text editor like Notepad or Notepad++.
    • I open a second Windows Explorer window, navigate to the .git folder of a project whose configuration I’d like to imitage, and open its file config as well.
    • I leave all of the details under [core] alone in the new .git\config file and add a new line. From the latter config file, I copy and paste in everything else – details like [remote "origin"], [branch "main"], and [user] – and do a bit of surgery. For example, I pick what I’m going to call my copy of this repository when I store it on, I make sure that my main branch is called main both locally and remotely, and I set myself up to register my commits using my private e-mail address for the account under which I’ll be storing a copy of this repository on GitHub, rather than my real e-mail address.
    • I ended up only copying in the [user] bit about the e-mail address for now, while retracing my steps to write this article.
    • I close out the config files, close out the extra Windows Explorer window, and navigate my main Windows Explorer window back up to c:\myfolder.

VSCode setup

  • I open VSCode and make sure any other projects that were open are completely closed out.
  • In the menu bar, I choose File -> Open Folder and, in the Open Folder popup, paste c:\myfolder into the “Folder” prompt at the bottom of the popup, hit Enter, and click Select Folder.
  • VSCode’s window title now says “Get Started - prod - Visual Studio Code” and if I click the top file-tree Explorer button at the left of VSCode (it looks like a piece of paper with the corner folded over, hovering over another piece of paper), I see in the left-hand navigation tray that there’s an empty area titled “PROD” with little “new file,” “new folder,” etc. icons to the right of it.
  • I close out the Get Started tab – I’ve been in VSCode before and I hate tab clutter (I am not one of those people w/ 900 browser tabs chronically open).
  • I hit F1 and search for “Peacock,” which is an extension I installed into VSCode to help me remember which editor window I’m working by relying on color. I choose Peacock: Surprise Me with a Random Color and decide if I like the color.
  • If I don’t, I keep hitting F1 + Enter to repeat the process until the color scheme feels “right” for the org I’m working on.
  • This creates the first folder under c:\myfolder – it’s called .vscode, and inside it is a file called settings.json.

First Git commit

  • Looking in the sidebar at the left side of VSCode, the little fork-with-bubbles-on-it icon has a circle with the number 1 in it. Clicking it, I can see that Git has tracked settings.json under .vscode as Changes. It wouldn’t be friendly to override someone else’s VSCode settings if they downloaded a copy of my Git repository representing the contents of c:\myfolder, so I’d like to make sure that I don’t accidentally track the contents of the .vscode folder.
  • I click the new file icon in the file explorer next to the folder label PROD (it looks like a piece of paper with the top right corner folded down, and a plus sign hovering over its lower right corner) and create one called .gitignore.
  • I fill it with the following body:
      # Tooling and misc.
  • Going back to look at the Git changes again, now I only see .gitignore as a proposed change. That’s better – I do, indeed, want to make “which files not to track” part of the codebase I’m tracking.
  • I assert that I’d like to track the .gitignore file by clicking on .gitignore under Changes and clicking the little plus sign + icon to move it to Staged Changes. (This is like doing git add at a command line.)
  • I officially log the change by typing Initial GitIgnore setup in the Message prompt and clicking the checkmark icon above the message prompt, to the right of the words “Source Control.” (This is like doing git commit at a command line.)

SFDX configuration files

  • I bring up a new Windows operating system command line prompt that is running in the c:\myfolder> context by hitting Ctrl + `.
  • I click the new file icon in the file explorer next to the folder label PROD and create one at the top of my folder structure (at the same level of nesting as .gitignore) called sfdx-project.json. I populate it with the following content (note: change sourceApiVersion to something appropriate for the time when you’re reading this article – version 53.0 went with the Winter ‘22 release):
      "packageDirectories": [
          "path": "force-app",
          "default": true
      "namespace": "",
      "sourceApiVersion": "53.0"
  • I click the new folder icon in the file explorer next to the folder label PROD (it looks like a hanging file folder with a plus sign hovering over its lower right corner) and create one called .sfdx.
  • In the file explorer left-area of VSCode, I right click on the new .sfdx folder and click New File and call it sfdx-config.json.
    • If the purpose of this folder were to track changes for a specific org aliased my-clever-alias, I’d I populate it with the following content:
          "defaultusername": "my-clever-alias"
    • On the other hand, if the purpose of this folder were to develop a somewhat “org-agnostic” framework and all I needed was some sort of dev hub I could use for the purpose of spinning up scratch orgs, I’d pick some dev hub I’d already connected to the Salesforce CLI – again, let’s say I’d aliased it my-clever-alias – and would instead write:
          "defaultdevhubusername": "my-clever-alias"
  • If I set a defaultusername, when I run the sfdx force:org:display command from the Terminal built into the bottom right area of VSCode, I should see a message like this:
      WARNING: This command will expose sensitive information ...
      === Org Description
      KEY               VALUE
      Alias             my-clever-alias
      Client Id         PlatformCLI
      Connected Status  Connected
    • If I made a typo in the alias name in the JSON file, I’ll see ERROR running force:org:display: No authorization information found for username the-typo-i-made.
    • If my computer’s installation of the Salesforce CLI isn’t logged into my-clever-alias, I’ll see ERROR running force:org:display: No authorization information found for username my-clever-alias. But I probably shouldn’t have gotten to this point, because it’s unlikely that my-clever-alias is still in sfdx auth:list if I’m getting this error message, so I can just pretend like I never set up this org with SFDX and go back and follow the instructions to do that.

I also need to create a hierarchy of empty folders until a folder called c:\myfolder\force-app\default\ exists, because I told sfdx-project.json that it would exist. (Otherwise, commands like sfdx force:mdapi:convert fail, and I like that command.)

SFDX global authentication and aliasing

I found that having a c:\myfolder\manifest\downloadme.xml file doesn’t expose an SFDX: Deploy Source in Manifest to Org right-click menu option in VSCode unless you properly did the whole F1 -> SFDX: Authorize an Org flow (although perhaps copying the indexes, tools, and typings folders out of .sfdx from another project would help), so I guess maybe all that global authentication stuff was a bit of a waste of time if you’re looking to get all of the right-click integrations up and running properly.

CCI project authentication and aliasing

If I’m going to need a production, sandbox, or dev org as a “persistent org” available for using with CumulusCI commands (e.g. for seeing how my mini-code does in a real sandbox), and if I didn’t already alias that org “globally,” now is the time to set it up from within the VSCode terminal that’s prompting me with c:\myfolder> (you can leave off the –login-url bit if you don’t use custom domains):

cci org connect my-clever-alias --login-url

Or, for a sandbox:

cci org connect my-clever-alias --sandbox --login-url


  1. cci org list, when run from a c:\myfolder> has my-clever-alias in it under the name column in the connected orgs table.
  2. If I open Windows command prompt (Windows key -> cmd) and do cd c:\example and execute cci org list from a c:\example> prompt, my-clever-alias is missing from the connected orgs, and that’s a good thing, because if I’m doing this kind of setup, I don’t want this org to be available as an alias to cci commands unless I’m executing them from the context of my project’s folder on my hard drive.

CCI configuration files

In the VSCode terminal pane at the C:\myfolder> prompt, if I run the command:

cci project info

I get an error message:

Error: The file cumulusci.yml was not found in the repo root: C:\myfolder. Are you in a CumulusCI Project directory?

To get the error message to go away, all I need to do is create an empty C:\myfolder\cumulusci.yml file.

There’s a lot that goes into writing a great cumulusci.yml file, much of which I should really be boilerplating out with the cci init command for actual installable codebases around a business process.

However, if I know c:\myfolder is for org backup only, and that I’m never going to be pushing things back into Salesforce orgs of any type, then creating an empty cumulusci.yml file is literally all I need to do to get a few of my favorite obscure code-downloading CumulusCI CLI commands to work from the C:\myfolder> prompt, like:

cci task run retrieve_unpackaged --path .ignoreme\fresh_downloads --org my-clever-alias --package-xml .ignoreme\weird-little-download-specification.xml


cci task run dx_convert_to --extra "--rootdir=.ignoreme\fresh_downloads --outputdir=.ignoreme\fresh_downloads_better_format"

If you’re going to actually want to create scratch orgs with CumulusCI using their standard aliases like beta, dev, etc., then you’ll also need to define baselines for them.

In a folder called c:\myfolder\orgs, you’ll need create a JSON-formatted file for each one that you think you might ever use. But this is definitely where you really ought to be using cci project init instead of trying to write these files yourself. It’s a waste of time to write them yourself. (Delete your empty cumulusci.yml file before running it, if you followed that step of this article.)

I haven’t yet really gotten into using I feel like the empty .cci folder, the .github folder, the, the datasets\mapping.yml file, or the robot folder – it’s mostly that orgs folder and a decent cumulusci.yml file I’m after – but cci project init does a perfectly respectable job of setting things up. And, hey, I just told you what folders it made that you can delete for now if you’re bothered by “clutter” like me and aren’t yet leveraging their features and promise you know how to do some “surgery” from a fresh project if you end up needing them later.

Now that orgs has the files it needs, I can run a scratch-org-related CumulusCI CLI command like:

cci task run deploy --org beta --path force-app\main\default

(Whenever I get an ERROR running force:org:create: This command requires a dev hub org username set either with a flag or by default in the config. message, I probably forgot to set a value for defaultdevhubusername in c:\myfolder\sfdx-project.json.)

If I actually had anything of interest in c:\myfolder\force-app\main\default, I could do this and go to setup through the web and see that my customizations indeed exist in the beta scratch org:

cci org browser --org beta

Actually dealing with files

So …

  • What’s the best way to download & upload code and configuration files between your hard drive and actual Salesforce orgs? (SFDX commands? Right-click VSCode commands? CCI commands? Which ones, and when? How should you break up configuration files between folders or let them be redundant to other folders? etc.)
  • What’s the best way to download & upload code and configuration files between your hard drive and the copy of them stored somewhere like GitHub? (What should you name the remote repository? When should you bother to store a copy remotely? How often should you commit changes? etc.)

It depends.

It depends how experienced you are with the tooling. It depends how experienced your colleagues are with various forms of tooling. It depends whether you’re doing a sort of personal / throwaway folder or whether this is serious code you mean to share and deploy and maintain with other people.

What I’ve done here is take notes about my “minimum viable VSCode” setup – but then to actually get things done, I find myself having to go look at my “Random handy CumulusCI notes” article all the time because I’m always doing things in non-standard ways and I never develop good coworking habits.

On the other hand, I really like doing things “the hard way” because I feel like it helps me appreciate “the right way” explained in SFDX and CCI getting started guides a lot better.

My Salesforce metadatamanagement journey is still in progress, but I hope you’ve found something useful here in the meantime. Thanks for joining me on the road.

--- ---