diff --git a/changelog/fragments/operator-name-config-file.yaml b/changelog/fragments/operator-name-config-file.yaml
new file mode 100644
index 00000000000..dad5850c40b
--- /dev/null
+++ b/changelog/fragments/operator-name-config-file.yaml
@@ -0,0 +1,10 @@
+entries:
+  - description: >
+      Added `projectName` key to the PROJECT config file (v3-alpha+).
+    kind: addition
+    breaking: false
+    migration:
+      header: Add the `projectName` config key to your PROJECT file
+      body: >
+        Set the `projectName` key in your PROJECT file. If this key is not set,
+        the current working directory's base name will be used in various subcommands.
diff --git a/go.mod b/go.mod
index 9f02117ce9b..5af0810f148 100644
--- a/go.mod
+++ b/go.mod
@@ -43,7 +43,7 @@ require (
 	rsc.io/letsencrypt v0.0.3 // indirect
 	sigs.k8s.io/controller-runtime v0.6.0
 	sigs.k8s.io/controller-tools v0.3.0
-	sigs.k8s.io/kubebuilder v1.0.9-0.20200618125005-36aa113dbe99
+	sigs.k8s.io/kubebuilder v1.0.9-0.20200723213622-353f7a6ba73b
 	sigs.k8s.io/yaml v1.2.0
 )
 
diff --git a/go.sum b/go.sum
index c856d5c4d3f..4219ab6cda7 100644
--- a/go.sum
+++ b/go.sum
@@ -1024,8 +1024,8 @@ sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvO
 sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
 sigs.k8s.io/controller-tools v0.3.0 h1:y3YD99XOyWaXkiF1kd41uRvfp/64teWcrEZFuHxPhJ4=
 sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI=
-sigs.k8s.io/kubebuilder v1.0.9-0.20200618125005-36aa113dbe99 h1:wdt455ji+MywIGDGQVUQUEGHa8WiRy0sfr5YFn00HbA=
-sigs.k8s.io/kubebuilder v1.0.9-0.20200618125005-36aa113dbe99/go.mod h1:FGPx0hvP73+bapzWoy5ePuhAJYgJjrFbPxgvWyortM0=
+sigs.k8s.io/kubebuilder v1.0.9-0.20200723213622-353f7a6ba73b h1:FhUDioJh37CVomwCLftxP4AbW+gy/lw0QObz8QYe2VY=
+sigs.k8s.io/kubebuilder v1.0.9-0.20200723213622-353f7a6ba73b/go.mod h1:lkExAOdnNf9BGrvi4lWHCMo1fa6xtENt/QVwDhWpK+c=
 sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
 sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
diff --git a/internal/plugins/helm/v1/chartutil/chart.go b/internal/plugins/helm/v1/chartutil/chart.go
index 0792b5585d6..9a6e1fbca20 100644
--- a/internal/plugins/helm/v1/chartutil/chart.go
+++ b/internal/plugins/helm/v1/chartutil/chart.go
@@ -152,7 +152,7 @@ func CreateChart(projectDir string, opts CreateOptions) (*resource.Options, *cha
 		return nil, nil, fmt.Errorf("failed to load chart: %v", err)
 	}
 
-	log.Infof("Created %s", relChartPath)
+	fmt.Printf("Created %s\n", relChartPath)
 	return r, c, nil
 }
 
diff --git a/internal/plugins/helm/v1/init.go b/internal/plugins/helm/v1/init.go
index f60ea767079..a6ec89e6aa8 100644
--- a/internal/plugins/helm/v1/init.go
+++ b/internal/plugins/helm/v1/init.go
@@ -62,12 +62,21 @@ Writes the following files:
 `
 	ctx.Examples = fmt.Sprintf(`  $ %s init --plugins=%s \
       --domain=example.com \
-      --group=apps --version=v1alpha1 \
+      --group=apps \
+      --version=v1alpha1 \
       --kind=AppService
 
   $ %s init --plugins=%s \
+      --project-name=myapp
       --domain=example.com \
-      --group=apps --version=v1alpha1 \
+      --group=apps \
+      --version=v1alpha1 \
+      --kind=AppService
+
+  $ %s init --plugins=%s \
+      --domain=example.com \
+      --group=apps \
+      --version=v1alpha1 \
       --kind=AppService \
       --helm-chart=myrepo/app
 
@@ -99,14 +108,15 @@ Writes the following files:
       --domain=example.com \
       --helm-chart=/path/to/local/chart-archives/app-1.2.3.tgz
 `,
-		ctx.CommandName, plugin.KeyFor(Plugin{}),
-		ctx.CommandName, plugin.KeyFor(Plugin{}),
-		ctx.CommandName, plugin.KeyFor(Plugin{}),
-		ctx.CommandName, plugin.KeyFor(Plugin{}),
-		ctx.CommandName, plugin.KeyFor(Plugin{}),
-		ctx.CommandName, plugin.KeyFor(Plugin{}),
-		ctx.CommandName, plugin.KeyFor(Plugin{}),
-		ctx.CommandName, plugin.KeyFor(Plugin{}),
+		ctx.CommandName, pluginKey,
+		ctx.CommandName, pluginKey,
+		ctx.CommandName, pluginKey,
+		ctx.CommandName, pluginKey,
+		ctx.CommandName, pluginKey,
+		ctx.CommandName, pluginKey,
+		ctx.CommandName, pluginKey,
+		ctx.CommandName, pluginKey,
+		ctx.CommandName, pluginKey,
 	)
 
 	p.commandName = ctx.CommandName
@@ -116,13 +126,14 @@ Writes the following files:
 func (p *initPlugin) BindFlags(fs *pflag.FlagSet) {
 	fs.SortFlags = false
 	fs.StringVar(&p.config.Domain, "domain", "my.domain", "domain for groups")
+	fs.StringVar(&p.config.ProjectName, "project-name", "", "name of this project, the default being directory name")
 	p.apiPlugin.BindFlags(fs)
 }
 
 // InjectConfig will inject the PROJECT file/config in the plugin
 func (p *initPlugin) InjectConfig(c *config.Config) {
 	// v3 project configs get a 'layout' value.
-	c.Layout = plugin.KeyFor(Plugin{})
+	c.Layout = pluginKey
 	p.config = c
 	p.apiPlugin.config = p.config
 }
@@ -143,14 +154,17 @@ func (p *initPlugin) Run() error {
 
 // Validate perform the required validations for this plugin
 func (p *initPlugin) Validate() error {
-	// Check if the project name is a valid namespace according to k8s
-	dir, err := os.Getwd()
-	if err != nil {
-		return fmt.Errorf("error to get the current path: %v", err)
+
+	// Check if the project name is a valid k8s namespace (DNS 1123 label).
+	if p.config.ProjectName == "" {
+		dir, err := os.Getwd()
+		if err != nil {
+			return fmt.Errorf("error getting current directory: %v", err)
+		}
+		p.config.ProjectName = strings.ToLower(filepath.Base(dir))
 	}
-	projectName := filepath.Base(dir)
-	if err := validation.IsDNS1123Label(strings.ToLower(projectName)); err != nil {
-		return fmt.Errorf("project name (%s) is invalid: %v", projectName, err)
+	if err := validation.IsDNS1123Label(p.config.ProjectName); err != nil {
+		return fmt.Errorf("project name (%s) is invalid: %v", p.config.ProjectName, err)
 	}
 
 	defaultOpts := chartutil.CreateOptions{CRDVersion: "v1"}
@@ -184,10 +198,10 @@ func (p *initPlugin) PostScaffold() error {
 		return err
 	}
 
-	if !p.doAPIScaffold {
-		fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName)
-	} else {
+	if p.doAPIScaffold {
 		return p.apiPlugin.PostScaffold()
 	}
+
+	fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName)
 	return nil
 }
diff --git a/internal/plugins/helm/v1/plugin.go b/internal/plugins/helm/v1/plugin.go
index 74f5543221c..e981fdd5881 100644
--- a/internal/plugins/helm/v1/plugin.go
+++ b/internal/plugins/helm/v1/plugin.go
@@ -26,6 +26,7 @@ const pluginName = "helm" + plugins.DefaultNameQualifier
 var (
 	supportedProjectVersions = []string{config.Version3Alpha}
 	pluginVersion            = plugin.Version{Number: 1}
+	pluginKey                = plugin.KeyFor(Plugin{})
 )
 
 var (
diff --git a/internal/plugins/helm/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go b/internal/plugins/helm/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go
index 59d9a4593e5..2abb40cc6e9 100644
--- a/internal/plugins/helm/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go
+++ b/internal/plugins/helm/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go
@@ -18,8 +18,6 @@ limitations under the License.
 package kdefault
 
 import (
-	"fmt"
-	"os"
 	"path/filepath"
 
 	"sigs.k8s.io/kubebuilder/pkg/model/file"
@@ -31,8 +29,7 @@ var _ file.Template = &AuthProxyPatch{}
 // prometheus metrics for manager Pod.
 type AuthProxyPatch struct {
 	file.TemplateMixin
-
-	OperatorName string
+	file.ProjectNameMixin
 }
 
 // SetTemplateDefaults implements input.Template
@@ -45,18 +42,10 @@ func (f *AuthProxyPatch) SetTemplateDefaults() error {
 
 	f.IfExistsAction = file.Error
 
-	if f.OperatorName == "" {
-		dir, err := os.Getwd()
-		if err != nil {
-			return fmt.Errorf("error to get the current path: %v", err)
-		}
-		f.OperatorName = filepath.Base(dir)
-	}
-
 	return nil
 }
 
-const kustomizeAuthProxyPatchTemplate = `# This patch inject a sidecar container which is a HTTP proxy for the 
+const kustomizeAuthProxyPatchTemplate = `# This patch inject a sidecar container which is a HTTP proxy for the
 # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
 apiVersion: apps/v1
 kind: Deployment
@@ -81,5 +70,5 @@ spec:
         args:
         - "--metrics-addr=127.0.0.1:8080"
         - "--enable-leader-election"
-        - "--leader-election-id={{ .OperatorName }}"
+        - "--leader-election-id={{ .ProjectName }}"
 `
diff --git a/internal/plugins/helm/v1/scaffolds/internal/templates/config/kdefault/kustomization.go b/internal/plugins/helm/v1/scaffolds/internal/templates/config/kdefault/kustomization.go
index 15a1c2d1f6c..d0ac7b7dd8d 100644
--- a/internal/plugins/helm/v1/scaffolds/internal/templates/config/kdefault/kustomization.go
+++ b/internal/plugins/helm/v1/scaffolds/internal/templates/config/kdefault/kustomization.go
@@ -17,9 +17,7 @@ limitations under the License.
 package kdefault
 
 import (
-	"os"
 	"path/filepath"
-	"strings"
 
 	"sigs.k8s.io/kubebuilder/pkg/model/file"
 )
@@ -29,9 +27,7 @@ var _ file.Template = &Kustomization{}
 // Kustomization scaffolds the Kustomization file for the default overlay
 type Kustomization struct {
 	file.TemplateMixin
-
-	// Prefix to use for name prefix customization
-	Prefix string
+	file.ProjectNameMixin
 }
 
 // SetTemplateDefaults implements input.Template
@@ -44,27 +40,18 @@ func (f *Kustomization) SetTemplateDefaults() error {
 
 	f.IfExistsAction = file.Error
 
-	if f.Prefix == "" {
-		// use directory name as prefix
-		dir, err := os.Getwd()
-		if err != nil {
-			return err
-		}
-		f.Prefix = strings.ToLower(filepath.Base(dir))
-	}
-
 	return nil
 }
 
 const kustomizeTemplate = `# Adds namespace to all resources.
-namespace: {{ .Prefix }}-system
+namespace: {{ .ProjectName }}-system
 
 # Value of this field is prepended to the
 # names of all resources, e.g. a deployment named
 # "wordpress" becomes "alices-wordpress".
 # Note that it should also match with the prefix (text before '-') of the namespace
 # field above.
-namePrefix: {{ .Prefix }}-
+namePrefix: {{ .ProjectName }}-
 
 # Labels to add to all resources and selectors.
 #commonLabels:
@@ -74,7 +61,7 @@ bases:
 - ../crd
 - ../rbac
 - ../manager
-# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 
+# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
 #- ../prometheus
 
 patchesStrategicMerge:
diff --git a/internal/plugins/helm/v1/scaffolds/internal/templates/config/manager/manager.go b/internal/plugins/helm/v1/scaffolds/internal/templates/config/manager/manager.go
index 5b41b4c3b72..79b6a0759ef 100644
--- a/internal/plugins/helm/v1/scaffolds/internal/templates/config/manager/manager.go
+++ b/internal/plugins/helm/v1/scaffolds/internal/templates/config/manager/manager.go
@@ -18,8 +18,6 @@ limitations under the License.
 package manager
 
 import (
-	"fmt"
-	"os"
 	"path/filepath"
 
 	"sigs.k8s.io/kubebuilder/pkg/model/file"
@@ -30,12 +28,10 @@ var _ file.Template = &Manager{}
 // Manager scaffolds yaml config for the manager.
 type Manager struct {
 	file.TemplateMixin
+	file.ProjectNameMixin
 
 	// Image is controller manager image name
 	Image string
-
-	// OperatorName will be used to create the pods
-	OperatorName string
 }
 
 // SetTemplateDefaults implements input.Template
@@ -46,13 +42,6 @@ func (f *Manager) SetTemplateDefaults() error {
 
 	f.TemplateBody = managerTemplate
 
-	if f.OperatorName == "" {
-		dir, err := os.Getwd()
-		if err != nil {
-			return fmt.Errorf("error getting working directory: %v", err)
-		}
-		f.OperatorName = filepath.Base(dir)
-	}
 	return nil
 }
 
@@ -84,7 +73,7 @@ spec:
       - image: {{ .Image }}
         args:
         - "--enable-leader-election"
-        - "--leader-election-id={{ .OperatorName }}"
+        - "--leader-election-id={{ .ProjectName }}"
         name: manager
         resources:
           limits:
diff --git a/internal/plugins/helm/v1/scaffolds/internal/templates/config/rbac/manager_role.go b/internal/plugins/helm/v1/scaffolds/internal/templates/config/rbac/manager_role.go
index 4c3ba48cc97..43de1c402b9 100644
--- a/internal/plugins/helm/v1/scaffolds/internal/templates/config/rbac/manager_role.go
+++ b/internal/plugins/helm/v1/scaffolds/internal/templates/config/rbac/manager_role.go
@@ -248,7 +248,7 @@ type roleDiscoveryInterface interface {
 // discovery API to lookup each resource in the resulting manifest.
 // The role scaffold will have IsClusterScoped=true if the chart lists cluster scoped resources
 func (f *ManagerRoleUpdater) updateForChart(dc roleDiscoveryInterface) {
-	log.Info("Generating RBAC rules")
+	fmt.Println("Generating RBAC rules")
 
 	clusterResourceRules, namespacedResourceRules, err := generateRoleRules(dc, f.Chart)
 	if err != nil {
diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go
index 0e87a51098b..ce5409d5718 100644
--- a/internal/util/projutil/project_util.go
+++ b/internal/util/projutil/project_util.go
@@ -103,6 +103,7 @@ func CheckProjectRoot() error {
 	return nil
 }
 
+// TODO: remove this (should use os.Getwd() or Config.ProjectName).
 func MustGetwd() string {
 	wd, err := os.Getwd()
 	if err != nil {