This file documents important changes needed to upgrade your app's Shopify App version to a new major version.
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.
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.
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.
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- 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.
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.
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.
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
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.
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.
Support for Ruby 2.x has been dropped as it is no longer supported. You'll need to upgrade to 3.x.x
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.
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.
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.
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.
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.
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.
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
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.
- 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.
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.
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:)
# ...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
endWithin 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.
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.
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.
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.
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
- 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'
- CHANGE
include ShopifyApp::SessionStoragetoinclude ShopifyApp::ShopSessionStorage
- CHANGE if you are using shop sessions,
@shop_sessionwill need to be changed to@current_shopify_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.
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
You do not need a user model; a shop session is fine for most applications.
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.
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'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
endembedded_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>You will need to also follow the ShopifyAPI upgrade guide to ensure your app is ready to work with API versioning.