Dylan AndersenDylan Andersen's Docs
Cursor + SalesforceExpert Cursor

Packaging & Hand-off

2GP unlocked packages so you can stop emailing zip files to customers and partners

When your POC ships, the customer or partner needs to install it somewhere. The wrong answer is a zip file in email. The right answer is a 2GP unlocked package, an install URL, and two lines of release notes.

Packaging and Hand-off hero

Who this is for

This page assumes your POC is in a DX project and you have access to a Dev Hub. If you built everything in a Developer Edition org with Change Sets, you'll need to pull the metadata into a DX project first. The First Project and Source Tracking pages cover that.

The two kinds of packages

There are two packaging models. You want 2GP.

  • 1GP (first-generation): tied to a namespaced Dev Org. Used by ISVs on AppExchange. Avoid for SE work.
  • 2GP (second-generation): built from your DX project using the sf CLI. Unlocked and managed variants. Unlocked is what an SE wants for POCs.

Unlocked packages can be installed, modified in the target org, and uninstalled. They're the right shape for a POC hand-off. The customer can see everything, tweak what they need, and own the result.

Prerequisites

  1. A Dev Hub. If you're using an internal demo org that's already enabled, great. If you're on a scratch org you own, enable it: Setup > Dev Hub > Enable Dev Hub.
  2. Your DX project checked into git with sfdx-project.json correctly configured.
  3. The sf CLI authenticated against your Dev Hub: sf org login web --alias devhub --set-default-dev-hub.

One-time setup: declare the package

Update sfdx-project.json to declare what you're shipping:

{
  "packageDirectories": [
    {
      "path": "force-app",
      "default": true,
      "package": "CS Agent POC",
      "versionName": "Spring 2026",
      "versionNumber": "1.0.0.NEXT"
    }
  ],
  "name": "cs-agent-poc",
  "namespace": "",
  "sfdcLoginUrl": "https://login.salesforce.com",
  "sourceApiVersion": "62.0",
  "packageAliases": {}
}

Then create the package entry:

sf package create \
  --name "CS Agent POC" \
  --path force-app \
  --package-type Unlocked \
  --target-dev-hub devhub

That command writes a package alias into sfdx-project.json automatically. Commit the updated file.

The deploy loop

Every time you want to ship a new version, it's three commands.

1. Create a version

sf package version create \
  --package "CS Agent POC" \
  --installation-key-bypass \
  --wait 20 \
  --target-dev-hub devhub

This runs a validation build, produces an immutable version, and gives you a SubscriberPackageVersionId (starts with 04t...).

Installation key bypass

--installation-key-bypass skips the password-for-install requirement. Fine for internal POCs. For external customers you may want a real key so random people can't install your URL.

2. Promote the version (for production installs)

Unpromoted versions can only install into scratch orgs and sandboxes. To let someone install into production:

sf package version promote \
  --package 04tXX... \
  --target-dev-hub devhub

You can skip this for most POC hand-offs. Most customers install into a sandbox first anyway.

3. Hand over the install URL

The install URL is:

https://login.salesforce.com/packaging/installPackage.apexp?p0=04tXX...

Or for a sandbox:

https://test.salesforce.com/packaging/installPackage.apexp?p0=04tXX...

The customer clicks the link, logs in, and Salesforce walks them through the install. Much nicer than a zip file.

Getting Cursor to do the whole thing

Once you've done the one-time sf package create, you can ask the agent:

Create the next version of the CS Agent POC package. Use sf package version
create, install-key-bypass, wait 20. When you get the version ID, produce
three deliverables for me:

1. The install URL in plain text.
2. A one-paragraph release note for this version based on git log since the
   last tag.
3. A table of which permission sets the customer needs to assign after install.

Agent mode will run the command, parse the ID, and draft the hand-off content. See POCs & Hand-off Docs for where the note lives.

What to exclude

Some things don't belong in a package. Specifically:

  • Profiles (almost always. The customer has their own).
  • Personal Permission Set assignments.
  • Scratch-org-only settings.
  • Seed data.

Handle this with .forceignore and by keeping scratch-only metadata in a second package directory. A common layout:

{
  "packageDirectories": [
    {
      "path": "force-app",
      "default": true,
      "package": "CS Agent POC",
      "versionName": "Spring 2026",
      "versionNumber": "1.0.0.NEXT"
    },
    {
      "path": "demo-only",
      "default": false
    }
  ]
}

demo-only/ holds seed data, demo user configs, and anything else you never ship. It still deploys to your own scratch orgs; it's just not part of the package.

Updating an installed package

When the customer has v1.0 and you've shipped v1.1, the same install URL with the new 04t... ID upgrades them in place. Admins see the version bump in Setup > Installed Packages.

When to go managed, when to stay unlocked

Stay unlocked for:

  • POCs where the customer will extend or modify what you built.
  • Internal Salesforce work.
  • Partner SE work that's a starting point, not a finished product.

Consider managed second-generation for:

  • ISV partners shipping real products.
  • POCs where the customer has asked for signed, upgradeable delivery.

For almost all SE work, unlocked is right. If you're unsure, ask whoever owns the packaging strategy for your team.

Troubleshooting

Invalid package directory when running sf package version create

Your sfdx-project.json has a typo. The package name in packageDirectories must match what you gave sf package create. Check packageAliases at the bottom of the file. That's the source of truth.

Version create hangs

Most of the 20-minute wait is the Dev Hub running an implicit validation. You can watch progress in the Dev Hub org at Setup > Package Manager. If it times out, the error in the console usually points at a metadata deploy failure inside the validation org.

Missing feature Agentforce or similar feature errors during version create

The scratch org that's built for validation doesn't have the feature enabled. Add a definitionFile to your package directory pointing at a scratch-def that turns the feature on:

{
  "path": "force-app",
  "default": true,
  "package": "CS Agent POC",
  "versionName": "Spring 2026",
  "versionNumber": "1.0.0.NEXT",
  "definitionFile": "config/project-scratch-def.json"
}

Customer can't install

99% of the time it's one of three things: they're trying to install into production but the version isn't promoted, they don't have the Install Packages permission, or a namespace conflict with an existing managed package. Check the Salesforce install error message first. It's specific.

Verification

After a successful sf package version create, run:

sf package version list --package "CS Agent POC" --target-dev-hub devhub

You should see your new version in the list with a SubscriberPackageVersionId. If you see IsReleased: false, it means it's not yet promoted (fine for sandbox installs).

Next moves

On this page