Upgrading
December 5, 2025 · View on GitHub
This file documents important changes needed to upgrade your app's Shopify App version to a new major version.
Table of contents
General Advice
Although we strive to make upgrades as smooth as possible, some effort may be required to stay up to date with the latest changes to shopify_app.
We strongly recommend you avoid 'monkeypatching' any existing code from ShopifyApp, e.g. by inheriting from ShopifyApp and then overriding particular methods. This can result in difficult upgrades. If your app does so, you will need to carefully check the gem's internal changes when upgrading.
If you need to upgrade by more than one major version (e.g. from v18 to v20), we recommend doing one at a time. Deploy each into production to help to detect problems earlier.
We also recommend the use of a staging site which matches your production environment as closely as possible.
If you do run into issues, we recommend looking at our debugging tips.
Unreleased
(v23.0.0) Minimum Ruby 3.2 and Rails 7.1 required, Jobs moved to app/jobs/
The minimum supported versions have been updated:
- Ruby: 3.1 → 3.2
- Rails: 5.2.1 → 7.1
Additionally, ActiveJob classes have been moved from lib/shopify_app/jobs/ to app/jobs/shopify_app/ to fix loading issues with modern Rails versions and follow Rails conventions.
Why this change was made
Rails 7.1+'s improved autoloading behavior (via Zeitwerk) properly handles jobs in the app/jobs/ directory, loading them lazily when needed rather than eagerly during initialization.
Migration steps
For most apps: No changes needed. The jobs are internal to the gem and will be autoloaded correctly by Rails.
If your app has custom ActiveJob serializers that reference these jobs:
- Ensure you're on Rails 7.1+ before upgrading. Rails 7.1 Upgrade Guide
- Coordinate deployment timing with apps that have custom serializers
- Custom serializers will now register before jobs are loaded (correct timing)
If your app directly requires or references job file paths (rare):
# ❌ Old path (will break)
require 'shopify_app/jobs/webhooks_manager_job'
# ✅ No require needed - Rails autoloads from app/jobs/
# Jobs are available as ShopifyApp::WebhooksManagerJob
Additional dependency changes
- sprockets-rails: Now a required runtime dependency. Most Rails apps already include this, but if your app uses an alternative asset pipeline (e.g., Propshaft), you may need to add
sprockets-railsto your Gemfile.
(v23.0.0) - ShopSessionStorageWithScopes and UserSessionStorageWithScopes are deprecated
ShopSessionStorageWithScopes and UserSessionStorageWithScopes are now marked as deprecated and will be removed in v24.0.0 in favor of ShopSessionStorage and UserSessionStorage, which handle all session attributes automatically (including access_scopes, expires_at, refresh_token, and refresh_token_expires_at for shops).
Migration:
- Update your Shop model to use
ShopSessionStorage:
# Before
class Shop < ActiveRecord::Base
include ShopifyApp::ShopSessionStorageWithScopes
end
# After
class Shop < ActiveRecord::Base
include ShopifyApp::ShopSessionStorage
end
- Update your User model to use
UserSessionStorage:
# Before
class User < ActiveRecord::Base
include ShopifyApp::UserSessionStorageWithScopes
end
# After
class User < ActiveRecord::Base
include ShopifyApp::UserSessionStorage
end
- Optional: You can now opt-in to using expiring offline access tokens with automatic refresh. See the Sessions documentation for setup instructions.
Note: If you had custom access_scopes= or access_scopes methods in your models, these are no longer needed. The base concerns now handle these attributes automatically.
(v23.0.0) - Deprecated methods in CallbackController
The following methods from ShopifyApp::CallbackController have been deprecated in v23.0.0
perform_after_authenticate_jobinstall_webhooksperform_post_authenticate_jobs
If you have overwritten these methods in your callback controller to modify the behavior of the inherited CallbackController, you will need to
update your app to use configurable option config.custom_post_authenticate_tasks instead. See post authenticate tasks
for more information.
(v23.0.0) - Removed ShopifyApp::JWTMiddleware
The ShopifyApp::JWTMiddleware middleware has been removed in v23.0.0. This middleware was used to populate the following environment variables from the JWT session token:
request.env["jwt.token"]request.env["jwt.shopify_domain"]request.env["jwt.shopify_user_id"]request.env["jwt.expire_at"]
If you are using any of these variables in your app, you'll need to replace them. You can instead include the ShopifyApp::WithShopifyIdToken concern, which does the same JWT parsing as the middleware, and exposes the same values in the following helper methods:
shopify_id_tokenjwt_shopify_domainjwt_shopify_user_idjwt_expire_at
(v23.0.0) - Deprecated "ShopifyApp::JWT" class
The ShopifyApp::JWT class has been deprecated in v23.0.0. Use ShopifyAPI::Auth::JwtPayload
class from the shopify_api gem instead. A search and replace should be enough for this migration.
ShopifyAPI::Auth::JwtPayloadis a superset of theShopifyApp::JWTclass, and contains methods that were available inShopifyApp::JWT.ShopifyAPI::Auth::JwtPayloadraisesShopifyAPI::Errors::InvalidJwtTokenErrorif the token is invalid.
Upgrading to v22.2.0
Added new feature for zero redirect embedded app authorization flow - Token Exchange
A new embedded app authorization strategy has been introduced in v22.2.0 that eliminates the redirects that were previously necessary for OAuth.
It can replace the existing installation and authorization code grant flow.
See new embedded app authorization strategy for more information.
Upgrading to v22.0.0
Dropped support for Ruby 2.x
Support for Ruby 2.x has been dropped as it is no longer supported. You'll need to upgrade to 3.x.x
Renamed Controller Concerns
The following controller concerns have been renamed/replaced in v21.10.0 and have now been removed. To upgrade, please rename any usage in your apps's controllers that include them to the following:
| Old Deprecated Controller Concern | Replaced By New Controller Concern |
|---|---|
Authenticated | EnsureHasSession |
RequireKnownShop | EnsureInstalled |
The new names better reflect what assurances the including the controller concern provide. The new concern provide similar if not identical functionality as the concerns they replaced.
Remove ScripttagManager
Script tag usage has largely been replaced with the adoption of theme app extensions and thank you order status customization. The manager has been removed with this major release due to effective replacement and a goal to have parity in supported functionality across language stacks.
If you find yourself still using Scipt Tags and want to continue the pattern of declarative management of script tags this gem used to use, we recommend porting the logic the manager used in prior versions and implementing it in a post authentication job. This is the recommended flow to create script tags (or any other logic) for stores that install your app.
No longer rescue non-shopify API errors during customized OAuth flow
If you have customized authentication logic and are counting on the CallbackController to catch your error and redirect to login, you'll need to catch that error and redirect to login_url_with_optional_shop.
Upgrading to 21.3.0
The Itp controller concern has been removed from LoginProtection which is included by the Authenticated/EnsureHasSession controller concern.
If any of your controllers are dependant on methods from Itp then you can include ShopifyApp::Itp directly.
You may notice a deprecation notice saying, Itp will be removed in an upcoming version.
This is because we intend on removing Itp completely in v22.0.0, but this will work in the meantime.
Upgrading to v20.3.0
Calling LoginProtection#current_shopify_domain will no longer raise an error if there is no active session. It will now return a nil value. The internal behavior of raising an error on OAuth redirect is still in place, however. If you were calling current_shopify_domain in authenticated actions and expecting an error if nil, you'll need to do a presence check and raise that error within your app.
Upgrading to v20.2.0
All custom errors defined inline within the ShopifyApp gem have been moved to lib/shopify_app/errors.rb.
- If you rescue any errors defined in this gem, you will need to rename them to match their new namespacing.
Upgrading to v20.1.0
Note that the following steps are optional and only apply to embedded applications. However, they can improve the loading time of your embedded app at installation and re-auth.
- For embedded applications, update any controller that renders a full page reload (e.g: your home controller) to redirect using
Shopify::Auth.embedded_app_url, if theembeddedquery argument is not present or does not equal1. Example here - If your app already has a frontend that uses App Bridge, this gem now supports using that to redirect out of the iframe before OAuth. Example here
- In your
shopify_app.rbinitializer, configure.embedded_redirect_urlto the path of the route you added above. - If you don't set this route, then the
shopify_appgem will automatically load its own copy of App Bridge and perform this redirection without any additional configuration.
- In your
Upgrading to v19.0.0
There are several major changes in this release:
- A change of strategy regarding sessions: Due to security changes with browsers, support for cookie based sessions was dropped. JWT is now the only supported method for managing sessions.
- As part of that change, this update moves API authentication logic from this gem to the
shopify_apigem. - Previously the
shopify_apigem relied onActiveResource, an outdated library which was removed from Rails in 2012. v10 ofshopify_apihas a replacement approach which aims to provide a similar syntax, but changes will be necessary.
High-level process
- Delete
config/initializers/omniauth.rbas apps no longer need to initializeOmniAuthdirectly. - Delete
config/initializers/user_agent.rbasshopify_appwill set the rightUser-Agentheader for interacting with the Shopify API. If the app requires further information in theUser-Agentheader beyond what Shopify API requires, specify this in theShopifyAPI::Context.user_agent_prefixsetting. - Remove
allow_jwt_authentication=andallow_cookie_authentication=invocations fromconfig/initializers/shopify_app.rbas the decision logic for which authentication method to use is now handled internally by theshopify_apigem, using theShopifyAPI::Context.embedded_appsetting. - Follow the guidance for upgrading
shopify-api-ruby.
Specific cases
Shopify user ID in session
Previously, we set the entire app user object in the session object.
As of v19, since we no longer save the app user to the session (but only the shopify user id), we now store it as session[:shopify_user_id]. Please make sure to update any references to that object.
Webhook Jobs
It is assumed that you have an ActiveJob implementation configured for perform_later, e.g. Sidekiq.
Ensure your jobs inherit from ApplicationJob or ActiveJob::Base.
Add a new handle method to existing webhook jobs to go through the updated shopify_api gem.
class MyWebhookJob < ActiveJob::Base
extend ShopifyAPI::Webhooks::Handler
class << self
# new handle function
def handle(topic:, shop:, body:)
# delegate to pre-existing perform_later function
perform_later(topic: topic, shop_domain: shop, webhook: body)
end
end
# original perform function
def perform(topic:, shop_domain:, webhook:)
# ...
Temporary sessions
The new shopify_api gem offers a utility to temporarily create sessions for interacting with the API within a block.
This is useful for interacting with the Shopify API outside of the context of a subclass of AuthenticatedController.
ShopifyAPI::Auth::Session.temp(shop: shop_domain, access_token: shop_token) do |session|
# make invocations to the API
end
Within a subclass of AuthenticatedController, the current_shopify_session function will return the current active
Shopify API session, or nil if no such session is available.
Setting up ShopifyAPI::Context
The shopify_app initializer must configure the ShopifyAPI::Context. The Rails generator will generate a block in the shopify_app initializer. To do so manually, you can refer to after_initialize block in the template.
Upgrading to v18.1.2
Version 18.1.2 replaces the deprecated EASDK redirect with an App Bridge 2 redirect when attempting to break out of an iframe. This happens when an app is installed, requires new access scopes, or re-authentication because the login session is expired.
Upgrading to v17.2.0
Different SameSite cookie attribute behavior
To support Rails v6.1, the SameSiteCookieMiddleware was updated to configure cookies to SameSite=None if the app is embedded. Before this release, cookies were configured to SameSite=None only if this attribute had not previously been set before.
# same_site_cookie_middleware.rb
- cookie << '; SameSite=None' unless cookie =~ /;\s*samesite=/i
+ cookie << '; SameSite=None' if ShopifyApp.configuration.embedded_app?
By default, Rails v6.1 configures SameSite=Lax on all cookies that don't specify this attribute.
Upgrading to v13.0.0
Version 13.0.0 adds the ability to use both user and shop sessions, concurrently. This however involved a large change to how session stores work. Here are the steps to migrate to 13.x
Changes to config/initializers/shopify_app.rb
- REMOVE
config.per_user_tokens = [true|false]this is no longer needed - CHANGE
config.session_repository = 'Shop'Toconfig.shop_session_repository = 'Shop' - ADD (optional) User Session Storage
config.user_session_repository = 'User'
Shop Model Changes (normally app/models/shop.rb)
- CHANGE
include ShopifyApp::SessionStoragetoinclude ShopifyApp::ShopSessionStorage
Changes to the @shop_session instance variable (normally in app/controllers/*.rb)
- CHANGE if you are using shop sessions,
@shop_sessionwill need to be changed to@current_shopify_session.
Changes to Rails session
- CHANGE
session[:shopify]is no longer set. Usesession[:user_id]if your app uses user based tokens, orsession[:shop_id]if your app uses shop based tokens.
Changes to ShopifyApp::LoginProtection
ShopifyApp::LoginProtection
- CHANGE if you are using
ShopifyApp::LoginProtection#shopify_sessionin your code, it will need to be changed toShopifyApp::LoginProtection#activate_shopify_session - CHANGE if you are using
ShopifyApp::LoginProtection#clear_shop_sessionin your code, it will need to be changed toShopifyApp::LoginProtection#clear_shopify_session
Notes
You do not need a user model; a shop session is fine for most applications.
Upgrading to v11.7.0
Session storage method signature breaking change
If you override def self.store(auth_session) method in your session storage model (e.g. Shop), the method signature has changed to def self.store(auth_session, *args) in order to support user-based token storage. Please update your method signature to include the second argument.
Upgrading from v8.6 to v9.0.0
Configuration change
Add an API version configuration in config/initializers/shopify_app.rb
Set this to the version you want to run against by default. See Shopify API docs for versions available.
config.api_version = '2019-04'
Session storage change
You will need to add an api_version method to your session storage object. The default implementation for this is.
def api_version
ShopifyApp.configuration.api_version
end
Generated file change
embedded_app.html.erb the usage of shop_session.url needs to be changed to shop_session.domain
<script type="text/javascript">
ShopifyApp.init({
apiKey: "<%= ShopifyApp.configuration.api_key %>",
shopOrigin: "<%= "https://#{ @shop_session.url }" if @shop_session %>",
debug: false,
forceRedirect: true
});
</script>
is changed to
<script type="text/javascript">
ShopifyApp.init({
apiKey: "<%= ShopifyApp.configuration.api_key %>",
shopOrigin: "<%= "https://#{ @shop_session.domain }" if @shop_session %>",
debug: false,
forceRedirect: true
});
</script>
ShopifyAPI changes
You will need to also follow the ShopifyAPI upgrade guide to ensure your app is ready to work with API versioning.