Kirill Platonov
Kirill Platonov
Sep 26, 2021

Multiple environments for Shopify theme app extensions

When working with Shopify apps it’s usually easy to have multiple environments. We simply create a separate development store for development, staging, product, and deploy a dedicated app instance for each. But how we can use multiple environments for theme app extensions?

With app extensions, things are a bit more tricky. Every extension is attached to a Shopify app and has its own set of credentials: ID and UUID. App extension is usually located in theme-app-extension folder in your app and we use the Shopify CLI command to upload it to Shopify: shopify extension push. When we create an app extension it generates theme-app-extension/.env file that contains credentials for both Shopify app and extension. Without this file, we won’t be able to upload an extension. And as you may guess it will only have credentials for one app/extension at a time.

With this in mind, how we can push extension to multiple environments (eg development & production)? Well, only by editing theme-app-extension/.env and putting there correct credentials for the current environment. Sounds not great, yeah? Doing this manually is the simplest approach you can take. But let’s take a look at how we can automate this.

Here’s the approach that I use:

  1. Save API keys for each app environment in encrypted credentials files.
  2. Create config for app extension credentials for each environment.
  3. Setup CLI commands to automatically generate theme-app-extension/.env before running shopify extension push command and delete afterward.

Below I will show the implementation for Ruby on Rails. For Node the approach will be the same but you will need to figure out the implementation yourself.

Step 1. App credentials

By default apps generated with shopify_app gem store API keys in .env file (an example). We need to change that and switch to built-in Rails credentials. This will allow us to securely store credentials for multiple environments and easily access production API keys when we need to publish app extension to production.

Run the following command to open credentials file:

bin/rails credentials:edit

And put your development API keys in the following format:

shopify:
  api_key: KEY
  api_secret: SECRET

Close the file to save and encrypt credentials.

Do the same for production environment:

bin/rails credentials:edit -e production

And update Shopify config to use credentials:

# config/initializers/shopify_app.rb

ShopifyApp.configure do |config|
  # ...
  config.api_key = Rails.application.credentials.dig(:shopify, :api_key)
  config.secret = Rails.application.credentials.dig(:shopify, :api_secret)
end

For more details on Rails credentials read the official doc.

Step 2. App extension credentials

With extension credentials, everything is simpler. They don’t contain sensitive data so we can skip encryption.

Create config file:

# config/theme_extension.yml
development:
  title: Extension Name
  id: EXT_ID
  uuid: EXT_UUID

test:
  title: Extension Name
  id: EXT_ID
  uuid: EXT_UUID

production:
  title: Extension Name
  id: EXT_ID
  uuid: EXT_UUID

And setup config reference:

# config/application.rb

module AppName
  class Application < Rails::Application
    # ...

    # Theme Extension
    config.theme_extension = config_for(:theme_extension)
  end
end

Now we can read config like this:

Rails.application.config_for(:theme_extension)

Step 3. Rake tasks

Create the following rake file:

# lib/tasks/extension.rake

namespace :extension do
  desc "Show theme extension info for current environment"
  task info: :environment do
    with_dot_env do
      system("cd theme-app-extension; bundle exec shopify extension info")
    end
  end

  desc "Push theme extension for current environment"
  task push: :environment do
    with_dot_env do
      system("cd theme-app-extension; bundle exec shopify extension push")
    end
  end

  def env_vars
    config = Rails.application.config_for(:theme_extension)
    {
      "SHOPIFY_API_KEY"    => Rails.application.credentials.shopify.fetch(:api_key),
      "SHOPIFY_API_SECRET" => Rails.application.credentials.shopify.fetch(:api_secret),
      "EXTENSION_TITLE"    => config[:title],
      "EXTENSION_ID"       => config[:id],
      "EXTENSION_UUID"     => config[:uuid],
    }
  end

  def dot_env_path
    Rails.root.join("theme-app-extension/.env")
  end

  def create_dot_env
    File.open(dot_env_path, "w") do |file|
      env_vars.each do |key, value|
        file.write "#{key}=#{value}\n"
      end
    end
  end

  def delete_dot_env
    File.delete(dot_env_path) if File.exists?(dot_env_path)
  end

  def with_dot_env
    create_dot_env
    yield
    delete_dot_env
  end
end

In the code above #with_dot_env method wraps every Shopify CLI call and manages theme-app-extension/.env file creation and deletion. This allows us not to worry about credentials.

How to use it

Now we have two rake commands.

To fetch extension info:

bin/rake extension:info

To push extension to Shopify:

bin/rake extension:push

And we can easily switch environment:

RAILS_ENV=production bin/rake extension:info
RAILS_ENV=staging bin/rake extension:push

I think it’s a pretty nice approach to isolate the complexity of environment management. I used it for building my new FAST Product Color Swatch app and it works pretty well.

If you like reading about Shopify app development follow me on Twitter.