How to delete files when the app is uninstalled?

Topic summary

A developer is trying to automatically delete theme files (sections and templates) when their Shopify app is uninstalled. They’ve built a Node.js app using GraphQL that can successfully delete files when manually triggered via button click, but the deletion fails when called through the APP_UNINSTALLED webhook.

Technical Setup:

  • Using themeFilesDelete GraphQL mutation
  • Attempting to trigger deletion via APP_UNINSTALLED webhook
  • Code executes up to logging the uninstall payload, but the themeFileDelete function doesn’t execute afterward

Key Issues Identified:

  • The developer questions whether testing on a local environment is causing the problem
  • Uncertainty about whether a separate webhook URL is needed
  • One responder provided detailed code showing how to pass shop/session context to the deletion function and retrieve offline sessions

Critical Problem:
A community member points out a fundamental flaw: the APP_UNINSTALLED webhook fires after uninstallation is complete (past tense), meaning the app and authorization scopes are already gone. This makes it impossible to make authenticated API calls to delete files at that point.

Suggested Solution:
Implement a pre-uninstall process within the app that merchants can run before uninstalling, rather than relying on the post-uninstall webhook. Some apps use app blocks to avoid this issue entirely.

Status: Unresolved - the fundamental timing issue with the webhook approach remains.

Summarized with AI on November 3. AI used: claude-sonnet-4-5-20250929.

I have created an app, and with the admin’s panel, we can add any section to the theme. However, I want to ensure that when the app is uninstalled, any section and template files that were added also get deleted at the time of uninstallation.

My app is built using a Node.js template, and it fetches data from a GraphQL API.

Here is my code for deleting the theme file:

app.post('/api/theme/fileDelete', async (req, res) => {
  try {
    // Initialize Shopify client
    const client = new shopify.api.clients.Graphql({
      session: res.locals.shopify.session, 
    });

    const themeId = "gid://shopify/OnlineStoreTheme/146551537909"; // specify your theme ID
    const filesToDelete = ["templates/index3.liquid"]; // specify the files to delete

    // Set up GraphQL mutation to delete theme files
    let mutation = `mutation($themeId: ID!, $files: [String!]!) {
      themeFilesDelete(themeId: $themeId, files: $files) {
        deletedThemeFiles {
          filename
        }
        userErrors {
          field
          message
        }
      }
    }`;

    // Send the request
    const response = await client.request(mutation, { variables: { themeId, files: filesToDelete } });

    // Log the result and send a response
    console.log("Files deleted:", response.data);

    res.status(200).send({message: "Theme files deleted"});
  } catch (error) {
    console.error("Error deleting theme files on uninstall:", error);
    res.status(500).send({message: error});
  }
});

//or

const themeFileDelete = async (req,res) => {
  try {
    // Initialize Shopify client
    const client = new shopify.api.clients.Graphql({
      session: res.locals.shopify.session, 
    });

    const themeId = "gid://shopify/OnlineStoreTheme/146551537909"; // specify your theme ID
    const filesToDelete = ["templates/index3.liquid"]; // specify the files to delete

    // Set up GraphQL mutation to delete theme files
    let mutation = `mutation($themeId: ID!, $files: [String!]!) {
      themeFilesDelete(themeId: $themeId, files: $files) {
        deletedThemeFiles {
          filename
        }
        userErrors {
          field
          message
        }
      }
    }`;

    // Send the request
    const response = await client.request(mutation, { variables: { themeId, files: filesToDelete } });

    // Log the result and send a response
    console.log("Files deleted:", response.data);

    res.status(200).send({message:"delete theme file"})
    // Optionally, you can also send a confirmation response back to the shop (if needed)
  } catch (error) {
    console.error("Error deleting theme files on uninstall:", error);
    res.status(500).send({message:error})

  }
};

export default themeFileDelete;

I have also set up a webhook for when the app is uninstalled, and it is working fine. However, I want the theme file deletion API to run only when the uninstall webhook is triggered.

Note: I have created the uninstall webhook in the privacy.js file, and I need to figure out how to run the theme deletion function from index.js when the uninstall webhook is triggered.

Here’s how my webhook setup looks:

import { DeliveryMethod } from "@shopify/shopify-api";
import themeFileDelete from './index.js'
/**
 * @type {{[key: string]: import("@shopify/shopify-api").WebhookHandler}}
 */
export default {
  /**
   * Customers can request their data from a store owner. When this happens,
   * Shopify invokes this privacy webhook.
   *
   * https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks#customers-data_request
   */
  CUSTOMERS_DATA_REQUEST: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    callback: async (topic, shop, body, webhookId) => {
      const payload = JSON.parse(body);
      // console.log(payload,"payloadpayload")
      // Payload has the following shape:
      // {
      //   "shop_id": 954889,
      //   "shop_domain": "{shop}.myshopify.com",
      //   "orders_requested": [
      //     299938,
      //     280263,
      //     220458
      //   ],
      //   "customer": {
      //     "id": 191167,
      //     "email": "john@example.com",
      //     "phone": "555-625-1199"
      //   },
      //   "data_request": {
      //     "id": 9999
      //   }
      // }
    },
  },

  /**
   * Store owners can request that data is deleted on behalf of a customer. When
   * this happens, Shopify invokes this privacy webhook.
   *
   * https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks#customers-redact
   */
  CUSTOMERS_REDACT: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    callback: async (topic, shop, body, webhookId) => {
      const payload = JSON.parse(body);
      // console.log(payload,"payload2")
      // Payload has the following shape:
      // {
      //   "shop_id": 954889,
      //   "shop_domain": "{shop}.myshopify.com",
      //   "customer": {
      //     "id": 191167,
      //     "email": "john@example.com",
      //     "phone": "555-625-1199"
      //   },
      //   "orders_to_redact": [
      //     299938,
      //     280263,
      //     220458
      //   ]
      // }
    },
  },

  /**
   * 48 hours after a store owner uninstalls your app, Shopify invokes this
   * privacy webhook.
   *
   * https://shopify.dev/docs/apps/webhooks/configuration/mandatory-webhooks#shop-redact
   */
  SHOP_REDACT: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    callback: async (topic, shop, body, webhookId) => {
      const payload = JSON.parse(body);
      // console.log(payload,"payload3")
      // console.log(payload,"appuninstall detata")
      // Payload has the following shape:
      // {
      //   "shop_id": 954889,
      //   "shop_domain": "{shop}.myshopify.com"
      // }
    },
  },

   /**
 * Webhook for when the app is uninstalled
 */
APP_UNINSTALLED: {
  deliveryMethod: DeliveryMethod.Http,
  callbackUrl: "/api/webhooks",
  callback: async (topic, shop, body, webhookId) => {

      const payload = JSON.parse(body);

      console.log(payload,"--------------Payload")
      console.log(topic,"--------------topic")
      console.log(shop,"--------------shop")
      console.log(webhookId,"--------------webhookId")
     
      await themeFileDelete()
  }
  
}

  

};
1 Like

To ensure that your theme file deletion function is triggered properly during the uninstall webhook, you need to structure your webhook to correctly call the deletion function with the required parameters. Here’s how you can optimize your implementation:

Key Points to Address1. Pass the Necessary Context to themeFileDelete:

  • The uninstall webhook should pass the shop and session information to the themeFileDelete function.
  1. Ensure themeFileDelete is Invoked with the Correct Parameters:

    • The function should accept parameters like shop and session since the uninstall webhook won’t have direct access to the res.locals.shopify.session.
  2. Update Your Webhook Callback:

    • Use the payload and the shop details from the uninstall webhook to authenticate the request and create a session for theme file deletion.

Updated Code Snippets#### 1. Update themeFileDelete to Accept Parameters

Modify your themeFileDelete function to accept the necessary parameters for processing.

const themeFileDelete = async (shop, session) => {
  try {
    const client = new shopify.api.clients.Graphql({ session });

    const themeId = "gid://shopify/OnlineStoreTheme/146551537909"; // Specify your theme ID
    const filesToDelete = ["templates/index3.liquid"]; // Files to delete

    const mutation = `mutation($themeId: ID!, $files: [String!]!) {
      themeFilesDelete(themeId: $themeId, files: $files) {
        deletedThemeFiles {
          filename
        }
        userErrors {
          field
          message
        }
      }
    }`;

    const response = await client.request(mutation, { variables: { themeId, files: filesToDelete } });
    console.log("Files deleted:", response.data);

    return { success: true, message: "Theme files deleted successfully" };
  } catch (error) {
    console.error("Error deleting theme files:", error);
    return { success: false, error };
  }
};

export default themeFileDelete;

2. Adjust the Uninstall Webhook Callback

Ensure the uninstall webhook creates a session for the shop and then invokes the themeFileDelete function.

APP_UNINSTALLED: {
  deliveryMethod: DeliveryMethod.Http,
  callbackUrl: "/api/webhooks",
  callback: async (topic, shop, body, webhookId) => {
    try {
      // Parse the webhook payload
      const payload = JSON.parse(body);

      console.log("Uninstall Payload:", payload);

      // Fetch the session for the shop
      const session = await shopify.utils.loadOfflineSession(shop);
      if (!session) {
        console.error("No session found for shop:", shop);
        return;
      }

      // Call the theme deletion function
      const result = await themeFileDelete(shop, session);
      if (result.success) {
        console.log("Theme files deleted successfully during app uninstallation.");
      } else {
        console.error("Error deleting theme files:", result.error);
      }
    } catch (error) {
      console.error("Error processing uninstall webhook:", error);
    }
  },
},

How It Works1. Webhook Triggers on Uninstallation:

  • Shopify sends the uninstall event, which your app handles through the APP_UNINSTALLED webhook.
  1. Session Retrieval:

    • The uninstall webhook fetches the session for the shop using Shopify’s session management utility (shopify.utils.loadOfflineSession).
  2. Invoke themeFileDelete:

    • The themeFileDelete function is called with the shop and session, ensuring the request is authenticated and properly scoped.
  3. File Deletion:

    • The GraphQL mutation deletes the specified files from the shop’s theme.

Testing- Test the app uninstallation process on a development store to ensure the theme files are deleted as expected.

  • Verify logs to confirm that:
    • The uninstall webhook is triggered.
    • The theme files are successfully deleted.

This structure ensures the theme files are removed only when the uninstall webhook is triggered, keeping your app and theme clean.

I updated the code and tried it, but it is still not working.

Is it possible that my app is not working because it’s local, or do we need to create a separate URL for the webhook?

Note: The code after console.log(“Uninstall Payload:”, payload); is not working?

It looks like you’re trying to ensure that when your app is uninstalled, any sections and template files added by the app are also deleted. You have set up a GraphQL mutation to delete files, and you want to ensure that this deletion occurs when the uninstall webhook is triggered.

From your code, you’re importing the themeFileDelete function in the webhook setup, but to ensure it works properly, you need to make sure that the uninstall webhook calls the themeFileDelete function correctly. The webhook needs to trigger the file deletion logic as part of the uninstallation process.

Here’s an updated approach with a few adjustments:

  1. Webhook Setup: Ensure that the webhook for APP_UNINSTALLED triggers the themeFileDelete function as expected.

  2. API Call: In the callback for the uninstall webhook, you can trigger the themeFileDelete function after receiving the uninstallation payload.

Updated Code:

import { DeliveryMethod } from "@shopify/shopify-api";
import themeFileDelete from './index.js';

export default {
  // Other webhooks...

  /**
   * Webhook for when the app is uninstalled
   */
  APP_UNINSTALLED: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    callback: async (topic, shop, body, webhookId) => {
      const payload = JSON.parse(body);
      console.log("Payload on app uninstall:", payload);
      console.log("Topic:", topic);
      console.log("Shop:", shop);
      console.log("Webhook ID:", webhookId);

      // Trigger the theme file deletion process
      try {
        await themeFileDelete();  // Ensure this function handles file deletion logic
      } catch (error) {
        console.error("Error during file deletion:", error);
      }
    }
  }
};

Additional Notes:- Ensure the themeFileDelete function is implemented properly and that it correctly communicates with Shopify’s GraphQL API to delete the files when the app is uninstalled.

  • Make sure your webhook is properly registered and triggers the right endpoint when the app is uninstalled.

By setting this up correctly, the theme files (such as index3.liquid in your example) will be deleted when the uninstall webhook is triggered.

@rohit1 This is the merchant app forums not developer api support.

use the community.shopify.dev api developer forums.

Convert app to use app blocks when possible.

The uninstalled webhook is uninstalled. not “uninstalling”.

..ed is PAST TENSE, it’s already happened.

The app and authorization scopes are gone.

And yes the shopify docs on this are below subpar for the authorization process and every other system touched by changes needing removal upon uninstallation, the omission of this very important fact of the processes arrow-of-time is a bit absurd.

This is why some apps use an pre-uninstall process built into the app that merchants can run in the app and not using the shopify admin uninstall process exclusively.

The rest is pretty much chatgpt generated misinformation making you chase your own tail.

I manually call the "themeFileDelete " function on a button click, and the file is deleted, but it is not being called in the webhook. I have tried many times.