diff --git a/packages/db/drizzle/PostgreSQL/0028_instagram_profile_preload.sql b/packages/db/drizzle/PostgreSQL/0028_instagram_profile_preload.sql
new file mode 100644
index 0000000..40c8904
--- /dev/null
+++ b/packages/db/drizzle/PostgreSQL/0028_instagram_profile_preload.sql
@@ -0,0 +1,406 @@
+-- Update the published Instagram handler without replacing unrelated template fields.
+UPDATE "templates"
+SET "custom_handlers" = jsonb_set("custom_handlers", '{requestHandler,code,source}', to_jsonb($ig_handler$
+const extractKeyedPayloadFromHtml = (html, targetKey = 'xdt_api__v1__profile_timeline') => {
+ const results = [];
+ if (typeof html !== 'string' || html.length === 0) return results;
+
+ const matches = [];
+ // Only target `;
+ const wait = jest.fn();
+
+ const result = await run({
+ request: { url: 'https://www.instagram.com/masonthames/' },
+ html,
+ preNav: { has: async () => false, wait },
+ });
+
+ expect(wait).not.toHaveBeenCalled();
+ expect(result.jsonResult).toEqual([expect.objectContaining({
+ type: 'profile', username: 'masonthames', followers_count: 3306694,
+ })]);
+ expect(result.markdown).toContain('Mason Thames');
+ });
+
+ it('fails when a profile page has no public profile data', async () => {
+ await expect(run({
+ request: { url: 'https://www.instagram.com/masonthames/' },
+ html: '',
+ })).rejects.toThrow('No public Instagram data was extracted');
+ });
+});
diff --git a/templates/instagram-scraper/requestHandler.js b/templates/instagram-scraper/requestHandler.js
index 262bbcc..20d0a7d 100644
--- a/templates/instagram-scraper/requestHandler.js
+++ b/templates/instagram-scraper/requestHandler.js
@@ -72,6 +72,35 @@ const extractKeyedPayloadFromHtml = (html, targetKey = 'xdt_api__v1__profile_tim
return timelineItems;
};
+// Logged-out profile pages now preload public profile data under
+// xig_user_by_username instead of requesting web_profile_info.
+const extractProfileFromHtml = (html, username) => {
+ if (typeof html !== 'string' || !html || !username) return null;
+ const scripts = /