Export a query to a different app

I often prototype some functionality in one app and then move the functionality to the final app.

I would love to be able to just export the queries to the other app rather than copy/past everything which can take a while.

Another use case is when I split up an app it into two or more apps and need to move the queries around. I often make a duplicate app and then prune each down, but sometimes I would rather prune one and build up the other.

6 Likes

Hey Bradley! Not sure if you are aware, but when you export an app via JSON the queries will be exported as well. If you keep the resource names the same and connect them first before importing the app, the queries will automatically connect. Will this work for your use case or is there something else you had in mind? For example, are you using multiple instances of Retool or Query Library queries?


{"uuid":"d4e0df48-e12a-11ec-94af-3fd5b3bb077e","page":{"id":75443235,"data":{"appState":"[\"~#iR\",[\"^ \",\"n\",\"appTemplate\",\"v\",[\"^ \",\"isFetching\",false,\"plugins\",[\"~#iOM\",[\"query2\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"query2\",\"type\",\"datasource\",\"subtype\",\"RESTQuery\",\"namespace\",null,\"resourceName\",\"5d925abc-a78f-4640-a282-17b20dbc4d08\",\"resourceDisplayName\",\"BoredAPI\",\"template\",[\"^3\",[\"queryRefreshTime\",\"\",\"paginationLimit\",\"\",\"body\",\"\",\"lastReceivedFromResourceAt\",null,\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",true,\"paginationPaginationField\",\"\",\"headers\",\"\",\"showFailureToaster\",true,\"paginationEnabled\",false,\"query\",\"\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"~#iL\",[]],\"runWhenPageLoadsDelay\",\"\",\"data\",null,\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",true,\"cacheKeyTtl\",\"\",\"cookies\",\"\",\"metadata\",null,\"changesetObject\",\"\",\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"watchedParams\",[\"^:\",[]],\"enableErrorTransformer\",false,\"showLatestVersionUpdatedWarning\",false,\"paginationDataField\",\"\",\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"runWhenPageLoads\",false,\"transformer\",\"// type your code here\\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\\nreturn data\",\"events\",[\"^:\",[]],\"queryTimeout\",\"10000\",\"requireConfirmation\",false,\"type\",\"GET\",\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^:\",[]],\"bodyType\",\"json\",\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"style\",null,\"position2\",null,\"mobilePosition2\",null,\"mobileAppPosition\",null,\"tabIndex\",null,\"container\",\"\",\"createdAt\",\"~m1654033467879\",\"updatedAt\",\"~m1654033467879\",\"folder\",\"\",\"screen\",null]]],\"query3\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"query3\",\"^4\",\"datasource\",\"^5\",\"SqlQueryUnified\",\"^6\",null,\"^7\",\"onboarding_db\",\"^8\",\"onboarding_db\",\"^9\",[\"^3\",[\"queryRefreshTime\",\"\",\"records\",\"\",\"lastReceivedFromResourceAt\",null,\"databasePasswordOverride\",\"\",\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",true,\"showFailureToaster\",true,\"query\",\"select * from users;\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"^:\",[]],\"runWhenPageLoadsDelay\",\"\",\"warningCodes\",[\"^:\",[]],\"data\",null,\"recordId\",\"\",\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",true,\"dataArray\",[\"^:\",[]],\"cacheKeyTtl\",\"\",\"filterBy\",\"\",\"databaseHostOverride\",\"\",\"metadata\",null,\"editorMode\",\"sql\",\"actionType\",\"\",\"changesetObject\",\"\",\"shouldUseLegacySql\",false,\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"databaseNameOverride\",\"\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"watchedParams\",[\"^:\",[]],\"enableErrorTransformer\",false,\"enableBulkUpdates\",false,\"showLatestVersionUpdatedWarning\",false,\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"bulkUpdatePrimaryKey\",\"\",\"runWhenPageLoads\",false,\"transformer\",\"// type your code here\\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\\nreturn data\",\"events\",[\"^:\",[]],\"tableName\",\"\",\"queryTimeout\",\"10000\",\"requireConfirmation\",false,\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^:\",[]],\"databaseUsernameOverride\",\"\",\"shouldEnableBatchQuerying\",false,\"doNotThrowOnNoOp\",false,\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",null,\"^?\",null,\"^@\",\"\",\"^A\",\"~m1654033471192\",\"^B\",\"~m1654033505871\",\"^C\",\"\",\"^D\",null]]],\"query4\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"query4\",\"^4\",\"datasource\",\"^5\",\"JavascriptQuery\",\"^6\",null,\"^7\",\"JavascriptQuery\",\"^8\",null,\"^9\",[\"^3\",[\"queryRefreshTime\",\"\",\"lastReceivedFromResourceAt\",null,\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",false,\"showFailureToaster\",true,\"query\",\"return \\\"Hi there friend\\\"\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"^:\",[]],\"runWhenPageLoadsDelay\",\"\",\"data\",null,\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",true,\"cacheKeyTtl\",\"\",\"metadata\",null,\"changesetObject\",\"\",\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"watchedParams\",[\"^:\",[]],\"enableErrorTransformer\",false,\"showLatestVersionUpdatedWarning\",false,\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"runWhenPageLoads\",false,\"transformer\",\"// type your code here\\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\\nreturn data\",\"events\",[\"^:\",[]],\"queryTimeout\",\"10000\",\"requireConfirmation\",false,\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^:\",[]],\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",null,\"^?\",null,\"^@\",\"\",\"^A\",\"~m1654033473332\",\"^B\",\"~m1654033514270\",\"^C\",\"\",\"^D\",null]]],\"query5\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"query5\",\"^4\",\"datasource\",\"^5\",\"GraphQLQuery\",\"^6\",null,\"^7\",\"a3be3231-579c-44fc-8ab0-f3e08b29a6da\",\"^8\",\"SpaceX\",\"^9\",[\"^3\",[\"queryRefreshTime\",\"\",\"paginationLimit\",\"\",\"body\",\"\\n{\\n  spaceX(arg: \\\"value\\\"){\\n    sugField\\n  }\\n}\",\"lastReceivedFromResourceAt\",null,\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",true,\"paginationPaginationField\",\"\",\"headers\",\"\",\"showFailureToaster\",true,\"paginationEnabled\",false,\"query\",\"\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"^:\",[]],\"runWhenPageLoadsDelay\",\"\",\"data\",null,\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"graphQLVariables\",\"[]\",\"showSuccessToaster\",true,\"cacheKeyTtl\",\"\",\"cookies\",\"\",\"metadata\",null,\"changesetObject\",\"\",\"errorTransformer\",\"//Use the variables data, metadata, and errors to reference these fields from your query's results\\nif (Array.isArray(errors) && errors.length > 0) {\\n  return errors[0].message;\\n} else {\\n  return false\\n}\\n    \",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"graphQLErrors\",null,\"watchedParams\",[\"^:\",[]],\"enableErrorTransformer\",false,\"showLatestVersionUpdatedWarning\",false,\"paginationDataField\",\"\",\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"runWhenPageLoads\",false,\"transformer\",\"// type your code here\\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\\nreturn data\",\"events\",[\"^:\",[]],\"queryTimeout\",\"10000\",\"requireConfirmation\",false,\"type\",\"POST\",\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^:\",[]],\"bodyType\",\"raw\",\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",null,\"^?\",null,\"^@\",\"\",\"^A\",\"~m1654033516219\",\"^B\",\"~m1654033557151\",\"^C\",\"\",\"^D\",null]]]]],\"^A\",null,\"version\",\"2.92.7\",\"appThemeId\",null,\"preloadedAppJavaScript\",null,\"preloadedAppJSLinks\",[],\"testEntities\",[],\"tests\",[],\"appStyles\",\"\",\"responsiveLayoutDisabled\",false,\"loadingIndicatorsDisabled\",false,\"urlFragmentDefinitions\",[\"^:\",[]],\"pageLoadValueOverrides\",[\"^:\",[]],\"customDocumentTitle\",\"\",\"customDocumentTitleEnabled\",false,\"customShortcuts\",[],\"isGlobalWidget\",false,\"isMobileApp\",false,\"multiScreenMobileApp\",false,\"folders\",[\"^:\",[]],\"queryStatusVisibility\",true,\"markdownLinkBehavior\",\"auto\",\"inAppRetoolPillAppearance\",\"NO_OVERRIDE\",\"rootScreen\",null,\"instrumentationEnabled\",false,\"experimentalPerfFeatures\",[\"^ \",\"batchCommitModelEnabled\",false,\"skipDepCycleCheckingEnabled\",false,\"serverDepGraphEnabled\",false,\"useRuntimeV2\",false],\"experimentalDataTabEnabled\",false]]]"},"changesRecord":[{"type":"DATASOURCE_TYPE_CHANGE","payload":{"newType":"GraphQLQuery","pluginId":"query5","resourceName":"a3be3231-579c-44fc-8ab0-f3e08b29a6da"}},{"type":"WIDGET_TEMPLATE_UPDATE","payload":{"plugin":{"id":"query5","type":"datasource","folder":"","subtype":"GraphQLQuery","template":{"body":"# GraphQL queries typically start with a \"{\" character. Lines that start\n# with a # are ignored.\n#\n# An example GraphQL query might look like:\n#\n#     {\n#       field(arg: \"value\") {\n#         subField\n#       }\n#     }\n#\n# Keyboard shortcuts:\n#\n#   Auto Complete:  Ctrl-Space (or just start typing)\n\n","data":null,"type":"POST","query":"","events":[],"cookies":"","headers":"","rawData":null,"bodyType":"raw","metadata":null,"changeset":"","timestamp":0,"isFetching":false,"isImported":false,"cacheKeyTtl":"","transformer":"// type your code here\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\nreturn data","queryTimeout":"10000","allowedGroups":[],"enableCaching":false,"graphQLErrors":null,"privateParams":[],"queryDisabled":"","watchedParams":[],"successMessage":"","changesetObject":"","paginationLimit":"","errorTransformer":"//Use the variables data, metadata, and errors to reference these fields from your query's results\nif (Array.isArray(errors) && errors.length > 0) {\n  return errors[0].message;\n} else {\n  return false\n}\n    ","graphQLVariables":"[]","queryRefreshTime":"","runWhenPageLoads":false,"changesetIsObject":false,"enableTransformer":false,"paginationEnabled":false,"queryThrottleTime":"750","queryTriggerDelay":"0","showFailureToaster":true,"showSuccessToaster":true,"importedQueryInputs":{},"paginationDataField":"","playgroundQueryUuid":"","requireConfirmation":false,"runWhenModelUpdates":true,"notificationDuration":"","queryDisabledMessage":"","resourceNameOverride":"","importedQueryDefaults":{},"playgroundQuerySaveId":"latest","runWhenPageLoadsDelay":"","enableErrorTransformer":false,"queryFailureConditions":"","paginationPaginationField":"","updateSetValueDynamically":false,"showLatestVersionUpdatedWarning":false,"showUpdateSetValueDynamicallyToggle":true},"container":"","createdAt":"2022-05-31T21:45:16.219Z","updatedAt":"2022-05-31T21:45:23.770Z","resourceName":"a3be3231-579c-44fc-8ab0-f3e08b29a6da"},"update":{"body":"\n{\n  spaceX(arg: \"value\"){\n    sugField\n  }\n}","data":null,"type":"POST","query":"","events":[],"cookies":"","headers":"","rawData":null,"bodyType":"raw","metadata":null,"changeset":"","timestamp":0,"isFetching":false,"isImported":false,"cacheKeyTtl":"","transformer":"// type your code here\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\nreturn data","queryTimeout":"10000","allowedGroups":[],"enableCaching":false,"graphQLErrors":null,"privateParams":[],"queryDisabled":"","watchedParams":[],"successMessage":"","changesetObject":"","paginationLimit":"","errorTransformer":"//Use the variables data, metadata, and errors to reference these fields from your query's results\nif (Array.isArray(errors) && errors.length > 0) {\n  return errors[0].message;\n} else {\n  return false\n}\n    ","graphQLVariables":"[]","queryRefreshTime":"","runWhenPageLoads":false,"changesetIsObject":false,"enableTransformer":false,"paginationEnabled":false,"queryThrottleTime":"750","queryTriggerDelay":"0","showFailureToaster":true,"showSuccessToaster":true,"importedQueryInputs":{},"paginationDataField":"","playgroundQueryUuid":"","requireConfirmation":false,"runWhenModelUpdates":true,"notificationDuration":"","queryDisabledMessage":"","resourceNameOverride":"","importedQueryDefaults":{},"playgroundQuerySaveId":"latest","runWhenPageLoadsDelay":"","enableErrorTransformer":false,"queryFailureConditions":"","paginationPaginationField":"","updateSetValueDynamically":false,"showLatestVersionUpdatedWarning":false,"showUpdateSetValueDynamicallyToggle":true},"widgetId":"query5"},"isUserTriggered":true}],"gitSha":null,"checksum":null,"createdAt":"2022-05-31T21:45:57.372Z","updatedAt":"2022-05-31T21:45:57.372Z","pageId":1177010,"userId":164920,"branchId":null},"modules":{}}

@alina.retool

Not really. That is really the same thing as just duplicating an app. I usually just want to copy some queries from one app into another. I already have the resources set up as I am copying within the same account typically, so that is not an issue.

Something like this.

image

You then get a popup asking which app to copy to or which file to export to. The dialog should remember the last used folder so I am not drilling down to the target app's folder for every copy - I don't have much hair left as it is.

If you had the export capability, you would also need the matching import a query into an app feature.

You would use export rather than copy if moving between accounts.

3 Likes

Ah, got it. This is a great request, and it makes a lot of sense! I'll let our engineering, product, and design team know about it. Thank you for taking the time to write this out and share it.

2 Likes

Any news on this? I would love to be able to copy queries between apps.

I had to manually copy a dozen queries from one app to another this morning. Trying to catch all of my configs like Disable query setting, transformers and onSuccess handlers is quite vexatious to the spirit!

Indeed!

I'll be using quite vexatious to the spirit in bug reports and feature requests going forward.

1 Like

We're actually doing a ton of work on reusability (including how we're thinking about the Query Library and reusable queries in general) right now and it'll be a top theme for us in 2024. We'll keep this thread updated with any news :slight_smile:

6 Likes

This would be incredibly valuable. Especially for prototyping early stage work (which I'm doing). I have potentially 4 iterations of an app that use a core set of (extensive) queries, transformers and variables. I really want to be able to copy a folder of aggregation and query functionality from one to another without have to duplicate the app, delete the ui, and start over. being able to copy components from one app to another in the UI with ctrl-c is amazing. we need that for resource folders!

Thank you for sharing this context, Eric! I shared it with the engineer managing this product :slight_smile:

+1 would love this feature

Added your +1 internally, thank you @darenhunter! :raised_hands:

+1

Thank you, @MiguelOrtiz!

1 Like

+1