Update theme assets in development phase

Please I’m working on a app that implements RTL feature, and i need to update the theme assets. How do I do that in development phase without getting an exemption from shopify since the app is not in the app store

Hi @Emmanuel_Kolawole you don’t need to go through the public app listing process. You can use a development store and a custom app setup to make changes directly to theme assets.

Please can you put me through. I’ve tried to several ways to update the assets, i keep geting a “not found” response. But when i try to retrive all assets in the theme, it works just fine

Here’s how you can do it:

1. Set up a dev store

  • Create a dev store: From the Partner Dashboard, create a new development store. Dev stores let you test custom apps and theme changes without affecting a live merchant store.
  • Install your custom app on the dev store: Once your app is created in your Partner Dashboard as a custom app, you can install it directly into your dev store. There’s no need for it to be listed on the Shopify App Store.

2. Use a custom app with required API scopes

  • Create a private or custom app: In your Partner Dashboard, create a custom app and request the required permissions for theme customization. You’ll need scopes like read_themes and write_themes. These scopes will allow your app to programmatically access and update theme assets.
  • Obtain an access token: After installing the custom app on your dev store, generate an Admin API access token. This token will let you make API calls to the dev store’s Admin API.

3. Update theme assets via the Admin API

  • Identify the theme you want to modify: Use the Theme Admin API endpoints to list available themes, find the one you’re testing on, and get its ID.
  • Update assets: Once you have the theme ID, you can use the Asset endpoint (/admin/api/2023-07/themes/{theme_id}/assets.json) to upload or modify theme files.
    • For example, to add or modify a stylesheet for RTL support, you can PUT a .scss.liquid or .css file via the API.
    • Similarly, any JavaScript or Liquid templates required to implement RTL can be updated here.

Sample request (using cURL):

curl -X PUT \
  -H "X-Shopify-Access-Token: {access_token}" \
  -H "Content-Type: application/json" \
  https://{your-dev-store}.myshopify.com/admin/api/2023-07/themes/{theme_id}/assets.json \
  -d '{
      "asset": {
        "key": "assets/theme-rtl.css",
        "value": "body { direction: rtl; }"
      }
  }'

This updates/creates the theme-rtl.css file in the theme’s assets.

4. Preview changes without publishing

  • Preview via the online store editor: You can preview changes on your dev store without making them live. Go to Online Store > Themes in your dev store’s admin, locate your modified theme, and click on Customize.
  • Test RTL functionality: Ensure that the injected CSS or JavaScript is working as expected.

Exactly what I did, but got an error

public function applyRtlStyles($themeId)
    {
        $store = Auth::guard('shopify')->user();
        $client = new Rest($store->shopify_store, $store->shopify_access_token);

        $assetKey = 'assets/base.css'; // Default asset key

        try {
            // Check available assets
            $assetsResponse = $client->get("themes/$themeId/assets.json");
            $assetsList = $assetsResponse->getDecodedBody()['assets'] ?? [];

            $availableAssets = array_column($assetsList, 'key');
            Log::info('Available Assets:', $availableAssets);

            // Use theme.css as fallback if base.css doesn't exist
            if (!in_array($assetKey, $availableAssets)) {
                $assetKey = 'assets/base.css'; // Fallback asset
                if (!in_array($assetKey, $availableAssets)) {
                    throw new \Exception("$assetKey and fallback CSS files not found in theme assets.");
                }
            }

            // Fetch the existing CSS
            $response = $client->get(
                path: "themes/$themeId/assets.json",
                query: ['asset[key]' => $assetKey]
            );

            $responseBody = $response->getDecodedBody();
            Log::info('Fetched Asset:', $responseBody);

            $themeCss = $responseBody['asset']['value'] ?? '';

            // Add RTL styles
            $rtlStyles = "body { direction: rtl !important; }";
            $updatedCss = $themeCss . "\n" . $rtlStyles;



            // Update the asset
            $updateResponse = $client->put(
                path: "themes/$themeId/assets.json",
                body: [
                    'asset' => [
                        'key' => $assetKey,
                        'value' => $updatedCss
                    ]
                ]
            );

            Log::info('Update Response:', $updateResponse->getDecodedBody());

            if ($updateResponse->getStatusCode() === 200) {
                Log::info("RTL styles applied successfully to $assetKey");
            } else {
                throw new \Exception('Failed to update the theme asset: ' . $updateResponse->getReasonPhrase());
            }
        } catch (\Exception $e) {
            Log::error('Error applying RTL styles: ' . $e->getMessage());
            Log::error('Asset Key: ' . $assetKey . ', Theme ID: ' . $themeId);
        }
    }
[2024-12-18 16:41:42] production.INFO: Update Response: {"errors":"Not Found"} 
[2024-12-18 16:41:42] production.ERROR: Error applying RTL styles: Failed to update the theme asset: Not Found  
[2024-12-18 16:41:42] production.ERROR: Asset Key: assets/base.css, Theme ID: 132504715353  

Can you include in the screenshot the previous log lines printed by Log::info('Available Assets:', $availableAssets) and Log::info('Fetched Asset:', $responseBody)?

sure. It’s a pretty long list though

[2024-12-18 16:41:37] production.INFO: themes {"theme":[{"id":132504682585,"name":"Dawn","created_at":"2024-12-18T11:27:42-05:00","updated_at":"2024-12-18T11:28:58-05:00","role":"unpublished","theme_store_id":887,"previewable":true,"processing":false,"admin_graphql_api_id":"gid://shopify/Theme/132504682585"},{"id":132504715353,"name":"test-data","created_at":"2024-12-18T11:28:19-05:00","updated_at":"2024-12-18T11:28:58-05:00","role":"main","theme_store_id":null,"previewable":true,"processing":false,"admin_graphql_api_id":"gid://shopify/Theme/132504715353"},{"id":132504748121,"name":"debut-vintage-theme","created_at":"2024-12-18T11:28:19-05:00","updated_at":"2024-12-18T11:28:34-05:00","role":"unpublished","theme_store_id":null,"previewable":true,"processing":false,"admin_graphql_api_id":"gid://shopify/Theme/132504748121"}]} 
[2024-12-18 16:41:39] production.INFO: Available Assets: ["assets/base.css","assets/cart-drawer.js","assets/cart-notification.js","assets/cart.js","assets/collage.css","assets/collapsible-content.css","assets/component-accordion.css","assets/component-article-card.css","assets/component-card.css","assets/component-cart-drawer.css","assets/component-cart-items.css","assets/component-cart-notification.css","assets/component-cart.css","assets/component-collection-hero.css","assets/component-complementary-products.css","assets/component-deferred-media.css","assets/component-discounts.css","assets/component-facets.css","assets/component-image-with-text.css","assets/component-list-menu.css","assets/component-list-payment.css","assets/component-list-social.css","assets/component-loading-overlay.css","assets/component-localization-form.css","assets/component-mega-menu.css","assets/component-menu-drawer.css","assets/component-modal-video.css","assets/component-model-viewer-ui.css","assets/component-newsletter.css","assets/component-pagination.css","assets/component-pickup-availability.css","assets/component-predictive-search.css","assets/component-price.css","assets/component-product-model.css","assets/component-rating.css","assets/component-search.css","assets/component-show-more.css","assets/component-slider.css","assets/component-slideshow.css","assets/component-totals.css","assets/constants.js","assets/customer.css","assets/customer.js","assets/details-disclosure.js","assets/details-modal.js","assets/facets.js","assets/global.js","assets/localization-form.js","assets/magnify.js","assets/main-search.js","assets/media-gallery.js","assets/newsletter-section.css","assets/password-modal.js","assets/pickup-availability.js","assets/predictive-search.js","assets/product-form.js","assets/product-info.js","assets/product-modal.js","assets/product-model.js","assets/pubsub.js","assets/quick-add.css","assets/quick-add.js","assets/recipient-form.js","assets/search-form.js","assets/section-blog-post.css","assets/section-collection-list.css","assets/section-contact-form.css","assets/section-email-signup-banner.css","assets/section-featured-blog.css","assets/section-featured-product.css","assets/section-footer.css","assets/section-image-banner.css","assets/section-main-blog.css","assets/section-main-page.css","assets/section-main-product.css","assets/section-multicolumn.css","assets/section-password.css","assets/section-related-products.css","assets/section-rich-text.css","assets/selling-plans.js","assets/share.js","assets/show-more.js","assets/template-collection.css","assets/template-giftcard.css","assets/theme-editor.js","assets/video-section.css","config/settings_data.json","config/settings_schema.json","layout/password.liquid","layout/theme.liquid","locales/bg-BG.json","locales/cs.json","locales/cs.schema.json","locales/da.json","locales/da.schema.json","locales/de.json","locales/de.schema.json","locales/el.json","locales/en.default.json","locales/en.default.schema.json","locales/es.json","locales/es.schema.json","locales/fi.json","locales/fi.schema.json","locales/fr.json","locales/fr.schema.json","locales/hr-HR.json","locales/hu.json","locales/id.json","locales/it.json","locales/it.schema.json","locales/ja.json","locales/ja.schema.json","locales/ko.json","locales/ko.schema.json","locales/lt-LT.json","locales/nb.json","locales/nb.schema.json","locales/nl.json","locales/nl.schema.json","locales/pl.json","locales/pl.schema.json","locales/pt-BR.json","locales/pt-BR.schema.json","locales/pt-PT.json","locales/pt-PT.schema.json","locales/ro-RO.json","locales/ru.json","locales/sk-SK.json","locales/sl-SI.json","locales/sv.json","locales/sv.schema.json","locales/th.json","locales/th.schema.json","locales/tr.json","locales/tr.schema.json","locales/vi.json","locales/vi.schema.json","locales/zh-CN.json","locales/zh-CN.schema.json","locales/zh-TW.json","locales/zh-TW.schema.json","sections/announcement-bar.liquid","sections/apps.liquid","sections/cart-drawer.liquid","sections/cart-icon-bubble.liquid","sections/cart-live-region-text.liquid","sections/cart-notification-button.liquid","sections/cart-notification-product.liquid","sections/collage.liquid","sections/collapsible-content.liquid","sections/collection-list.liquid","sections/contact-form.liquid","sections/custom-liquid.liquid","sections/email-signup-banner.liquid","sections/featured-blog.liquid","sections/featured-collection.liquid","sections/featured-product.liquid","sections/footer-group.json","sections/footer.liquid","sections/header-group.json","sections/header.liquid","sections/image-banner.liquid","sections/image-with-text.liquid","sections/main-404.liquid","sections/main-account.liquid","sections/main-activate-account.liquid","sections/main-addresses.liquid","sections/main-article.liquid","sections/main-blog.liquid","sections/main-cart-footer.liquid","sections/main-cart-items.liquid","sections/main-collection-banner.liquid","sections/main-collection-product-grid.liquid","sections/main-list-collections.liquid","sections/main-login.liquid","sections/main-order.liquid","sections/main-page.liquid","sections/main-password-footer.liquid","sections/main-password-header.liquid","sections/main-product.liquid","sections/main-register.liquid","sections/main-reset-password.liquid","sections/main-search.liquid","sections/multicolumn.liquid","sections/multirow.liquid","sections/newsletter.liquid","sections/page.liquid","sections/pickup-availability.liquid","sections/predictive-search.liquid","sections/related-products.liquid","sections/rich-text.liquid","sections/slideshow.liquid","sections/video.liquid","snippets/article-card.liquid","snippets/buy-buttons.liquid","snippets/card-collection.liquid","snippets/card-product.liquid","snippets/cart-drawer.liquid","snippets/cart-notification.liquid","snippets/country-localization.liquid","snippets/email-signup-banner-background-mobile.liquid","snippets/email-signup-banner-background.liquid","snippets/facets.liquid","snippets/gift-card-recipient-form.liquid","snippets/icon-3d-model.liquid","snippets/icon-accordion.liquid","snippets/icon-account.liquid","snippets/icon-arrow.liquid","snippets/icon-caret.liquid","snippets/icon-cart-empty.liquid","snippets/icon-cart.liquid","snippets/icon-checkmark.liquid","snippets/icon-clipboard.liquid","snippets/icon-close-small.liquid","snippets/icon-close.liquid","snippets/icon-discount.liquid","snippets/icon-error.liquid","snippets/icon-facebook.liquid","snippets/icon-filter.liquid","snippets/icon-hamburger.liquid","snippets/icon-instagram.liquid","snippets/icon-minus.liquid","snippets/icon-padlock.liquid","snippets/icon-pause.liquid","snippets/icon-pinterest.liquid","snippets/icon-play.liquid","snippets/icon-plus.liquid","snippets/icon-remove.liquid","snippets/icon-share.liquid","snippets/icon-snapchat.liquid","snippets/icon-success.liquid","snippets/icon-tick.liquid","snippets/icon-tiktok.liquid","snippets/icon-tumblr.liquid","snippets/icon-twitter.liquid","snippets/icon-unavailable.liquid","snippets/icon-vimeo.liquid","snippets/icon-with-text.liquid","snippets/icon-youtube.liquid","snippets/icon-zoom.liquid","snippets/language-localization.liquid","snippets/meta-tags.liquid","snippets/pagination.liquid","snippets/price.liquid","snippets/product-media-gallery.liquid","snippets/product-media-modal.liquid","snippets/product-media.liquid","snippets/product-thumbnail.liquid","snippets/product-variant-options.liquid","snippets/product-variant-picker.liquid","snippets/selling-plans.liquid","snippets/share-button.liquid","snippets/social-icons.liquid","templates/404.json","templates/article.json","templates/blog.json","templates/cart.json","templates/collection.json","templates/customers/account.json","templates/customers/activate_account.json","templates/customers/addresses.json","templates/customers/login.json","templates/customers/order.json","templates/customers/register.json","templates/customers/reset_password.json","templates/gift_card.liquid","templates/index.json","templates/list-collections.json","templates/page.contact.json","templates/page.json","templates/password.json","templates/product.json","templates/search.json"] 
[2024-12-18 16:41:40] production.INFO: Fetched Asset: {"asset":{"key":"assets/base.css","public_url":"https://cdn.shopify.com/s/files/1/0623/1824/8025/t/2/assets/base.css?v=1734539306","value":"/* Color custom properties */

:root,
.color-background-1 {
  --color-foreground: var(--color-base-text);
  --color-background: var(--color-base-background-1);
  --gradient-background: var(--gradient-base-background-1);
}

.color-background-2 {
  --color-foreground: var(--color-base-text);
  --color-background: var(--color-base-background-2);
  --gradient-background: var(--gradient-base-background-2);
}



.ratio::before {
  content: '';
  width: 0;
  height: 0;
  padding-bottom: var(--ratio-percent);
}

.content-container {
  border-radius: var(--text-boxes-radius);
  border: var(--text-boxes-border-width) solid rgba(var(--color-foreground), var(--text-boxes-border-opacity));
  position: relative;
}

.content-container:after {
  content: '';
  position: absolute;
  top: calc(var(--text-boxes-border-width) * -1);
  right: calc(var(--text-boxes-border-width) * -1);
  bottom: calc(var(--text-boxes-border-width) * -1);
  left: calc(var(--text-boxes-border-width) * -1);
  border-radius: var(--text-boxes-radius);
  box-shadow: var(--text-boxes-shadow-horizontal-offset)
    var(--text-boxes-shadow-vertical-offset)
    var(--text-boxes-shadow-blur-radius)
    rgba(var(--color-shadow), var(--text-boxes-shadow-opacity));
  z-index: -1;
}

.content-container--full-width:after {
  left: 0;
  right: 0;
  border-radius: 0;
}

@media screen and (max-width: 749px) {
  .content-container--full-width-mobile {
    border-left: none;
    border-right: none;
    border-radius: 0;
  }
  .content-container--full-width-mobile:after {
    display: none;
  }
}

.global-media-settings {
  position: relative;
  border: var(--media-border-width) solid rgba(var(--color-foreground), var(--media-border-opacity));
  border-radius: var(--media-radius);
  overflow: visible !important;
  background-color: rgb(var(--color-background));
}

.global-media-settings:after {
  content: '';
  position: absolute;
  top: calc(var(--media-border-width) * -1);
  right: calc(var(--media-border-width) * -1);
  bottom: calc(var(--media-border-width) * -1);
  left: calc(var(--media-border-width) * -1);
  border-radius: var(--media-radius);
  box-shadow: var(--media-shadow-horizontal-offset) var(--media-shadow-vertical-offset) var(--media-shadow-blur-radius) rgba(var(--color-shadow), var(--media-shadow-opacity));
  z-index: -1;
  pointer-events: none;
}

.global-media-settings--no-shadow {
  overflow: hidden !important;
}

.global-media-settings--no-shadow:after {
  content: none;
}

.global-media-settings img,
.global-media-settings iframe,
.global-media-settings model-viewer,
.global-media-settings video {
  border-radius: calc(var(--media-radius) - var(--media-border-width));
}

.content-container--full-width,
.global-media-settings--full-width,
.global-media-settings--full-width img,
.global-media-settings--full-width video,
.global-media-settings--full-width iframe {
  border-radius: 0;
  border-left: none;
  border-right: none;
}

/* check for flexbox gap in older Safari versions */
@supports not (inset: 10px) {
  .grid {
    margin-left: calc(-1 * var(--grid-mobile-horizontal-spacing));
  }

  .grid__item {
    padding-left: var(--grid-mobile-horizontal-spacing);
    padding-bottom: var(--grid-mobile-vertical-spacing);
  }

  @media screen and (min-width: 750px) {
    .grid {
      margin-left: calc(-1 * var(--grid-desktop-horizontal-spacing));
    }

    .grid__item {
      padding-left: var(--grid-desktop-horizontal-spacing);
      padding-bottom: var(--grid-desktop-vertical-spacing);
    }
  }

  .grid--gapless .grid__item {
    padding-left: 0;
    padding-bottom: 0;
  }

  @media screen and (min-width: 749px) {
    .grid--peek .grid__item {
      padding-left: var(--grid-mobile-horizontal-spacing);
    }
  }

  .product-grid .grid__item {
    padding-bottom: var(--grid-mobile-vertical-spacing);
  }

  @media screen and (min-width: 750px) {
    .product-grid .grid__item {
      padding-bottom: var(--grid-desktop-vertical-spacing);
    }
  }
}

.font-body-bold {
  font-weight: var(--font-body-weight-bold);
}

/* outline and border styling for Windows High Contrast Mode */
@media (forced-colors: active) {
  .button,
  .shopify-challenge__button,
  .customer button {
    border: transparent solid 1px;
  }

  .button:focus-visible,
  .button:focus,
  .button.focused,
  .shopify-payment-button__button--unbranded:focus-visible,
  .shopify-payment-button [role=\"button\"]:focus-visible,
  .shopify-payment-button__button--unbranded:focus,
  .shopify-payment-button [role=\"button\"]:focus {
    outline: solid transparent 1px;
  }

  .field__input:focus,
  .select__select:focus,
  .customer .field input:focus,
  .customer select:focus,
  .localization-form__select:focus.localization-form__select:after {
    outline: transparent solid 1px;
  }

  .localization-form__select:focus {
    outline: transparent solid 1px;
  }
}

.rte:after {
  clear: both;
  content: '';
  display: block;
}

.rte > p:first-child {
  margin-top: 0;
}

.rte > p:last-child {
  margin-bottom: 0;
}

.rte table {
  table-layout: fixed;
}

@media screen and (min-width: 750px) {
  .rte table td {
    padding-left: 1.2rem;
    padding-right: 1.2rem;
  }
}

.rte img {
  height: auto;
  max-width: 100%;
  border: var(--media-border-width) solid rgba(var(--color-foreground), var(--media-border-opacity));
  border-radius: var(--media-radius);
  box-shadow: var(--media-shadow-horizontal-offset) var(--media-shadow-vertical-offset) var(--media-shadow-blur-radius) rgba(var(--color-shadow), var(--media-shadow-opacity));
  margin-bottom: var(--media-shadow-vertical-offset);
}

.rte ul,
.rte ol {
  list-style-position: inside;
  padding-left: 2rem;
}

.rte li {
  list-style: inherit;
}

.rte li:last-child {
  margin-bottom: 0;
}

.rte a {
  color: rgba(var(--color-link), var(--alpha-link));
  text-underline-offset: 0.3rem;
  text-decoration-thickness: 0.1rem;
  transition: text-decoration-thickness var(--duration-short) ease;
}

.rte a:hover {
  color: rgb(var(--color-link));
  text-decoration-thickness: 0.2rem;
}

.rte blockquote {
  display: inline-flex;
}

.rte blockquote > * {
  margin: -0.5rem 0 -0.5rem 0;
}

/* Ambient animation */

@media (prefers-reduced-motion: no-preference) {
  .animate--ambient > img,
  .animate--ambient > svg {
    animation: animateAmbient 30s linear infinite;
  }

  @keyframes animateAmbient {
    0% { transform: rotate(0deg) translateX(1em) rotate(0deg) scale(1.2); }
    100% { transform: rotate(360deg) translateX(1em) rotate(-360deg) scale(1.2); }
  }
}
","created_at":"2024-12-18T11:28:26-05:00","updated_at":"2024-12-18T11:28:26-05:00","content_type":"text/css","size":67156,"checksum":"176c18da5fb98484708787e620b2eb93","theme_id":132504715353,"warnings":[]}} 
[2024-12-18 16:41:42] production.INFO: Update Response: {"errors":"Not Found"} 
[2024-12-18 16:41:42] production.ERROR: Error applying RTL styles: Failed to update the theme asset: Not Found  
[2024-12-18 16:41:42] production.ERROR: Asset Key: assets/base.css, Theme ID: 132504715353 

I’m pretty sure you need to request an exception - I was just recently trying to use this API to write (create/update) to the themes and the api returns 404.

More info here:

Specifically, check the “Uses cases eligible for exemption”.

Alright. Thank you very much