How to delete files when the app is uninstalled?

How to delete files when the app is uninstalled?

rohit1
Shopify Partner
13 0 1

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()
  }
  
}

  


};

 

 

Replies 5 (5)

steve_michael2
Trailblazer
441 38 55

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 Address

  1. Pass the Necessary Context to themeFileDelete:

    • The uninstall webhook should pass the shop and session information to the themeFileDelete function.
  2. 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.
  3. 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 Works

  1. Webhook Triggers on Uninstallation:

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

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

    • The themeFileDelete function is called with the shop and session, ensuring the request is authenticated and properly scoped.
  4. 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.

rohit1
Shopify Partner
13 0 1

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?

steve_michael2
Trailblazer
441 38 55

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
Shopify Partner
13 0 1

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.

PaulNewton
Shopify Partner
7559 667 1596

@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.

 

Contact paull.newton+shopifyforum@gmail.com for the solutions you need


Save time & money ,Ask Questions The Smart Way


Problem Solved? ✔Accept and Like solutions to help future merchants

Answers powered by coffee Thank Paul with a Coffee for more answers or donate to eff.org