Local supporter

Displaying photos from URL

by | Jun 19, 2025 | How To, Learn, Paradigm

When I downloaded my list of plants and animals from GBIF, they came in with scientific names and no pictures. I couldn’t tell you what most of those species were, even if you told me their common name. I had to learn them all.

I wanted to display temporary photos to know what I was looking for as I dug through my own photos. How would I know what photo is which species? I asked iNaturalist.

Correction. This is a business that needs to come out of the red. Time is money I don’t have. I wanted 500 pictures in five minutes. I asked ChatGPT how I can ask iNaturalist’s API for shared species photos. After about 20 failed attempts, I finally got a script that gave me a URL of a photo for every species on my list. The script was a bit complicated because it achieved three goals for all 500 species.

I won’t bore you with the frustration of failed attempts. Here is what FINALLY worked using Google Apps Script to run it:

function fetchINatPhotosAndAttributions() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const rows = sheet.getDataRange().getValues();

  for (let i = 1; i < rows.length; i++) { // Skip header
    const taxonUrl = rows[i][2];
    if (!taxonUrl || !taxonUrl.includes("/taxa/")) continue;

    const taxonId = taxonUrl.split("/taxa/")[1];
    const apiUrl = `https://api.inaturalist.org/v1/taxa/${taxonId}`;

    try {
      const response = UrlFetchApp.fetch(apiUrl);
      const json = JSON.parse(response.getContentText());
      const photo = json.results[0].default_photo;
      if (!photo) continue;

      const photoUrl = photo.medium_url || "";
      let rawAttribution = photo.attribution || "Photo © Unknown";
      const licenseCode = photo.license_code || "";
      const licenseUrl = getLicenseUrl(licenseCode);
      const licenseLink = licenseUrl
        ? `<a href="${licenseUrl}">${licenseCode.toUpperCase()}</a>`
        : licenseCode;

      // 🔹 Remove "uploaded by ..." if present
      rawAttribution = rawAttribution.replace(/,?\s*uploaded by .+$/i, "");

      // 🔹 Remove existing license mention (e.g., "(CC BY-NC)" or "CC BY-NC")
      rawAttribution = rawAttribution.replace(/\(CC [^)]+\)/i, "").replace(/CC [A-Z\-]+/i, "").trim();

      // 🔹 Remove trailing commas or extra spaces
      rawAttribution = rawAttribution.replace(/,\s*$/, "").trim();

      const cleanAttribution = `${rawAttribution}${licenseLink ? " (" + licenseLink + ")" : ""}`;

      // Write photo URL to column D and attribution to column E
      sheet.getRange(i + 1, 4).setValue(photoUrl);
      sheet.getRange(i + 1, 5).setValue(cleanAttribution);

    } catch (e) {
      Logger.log(`Error at row ${i + 1}: ${e.message}`);
    }

    Utilities.sleep(200); // avoid throttling
  }
}

function getLicenseUrl(code) {
  const base = "https://creativecommons.org/licenses/";
  const map = {
    "cc0": "zero/1.0/",
    "cc-by": "by/4.0/",
    "cc-by-sa": "by-sa/4.0/",
    "cc-by-nd": "by-nd/4.0/",
    "cc-by-nc": "by-nc/4.0/",
    "cc-by-nc-sa": "by-nc-sa/4.0/",
    "cc-by-nc-nd": "by-nc-nd/4.0/"
  };
  return code.toLowerCase() in map ? base + map[code.toLowerCase()] : "";
}

This code gave me two new columns on my spreadsheet. I now had the iNaturalist photo URL and photographer for every species on my list.

Did you know you can create 500 or 5,000 posts in WordPress by uploading a spreadsheet? Well, technically, it’s a CSV file, which our home computers display as a spreadsheet. How you go about that is a whole other post. This one is about getting pictures for those posts from the internet.

When you insert an image into a WordPress post, WordPress offers you the choice to upload one from your computer or fetch one from the internet. Here. I’ll do it now:

HOWEVER, I wanted those species photos to be imported into the Featured Image field, which is outside of the body of the post. This allows you to use it as a thumbnail in lists and whatnot. The Insert from URL option is not available for Featured Images.

How did I get my 500 URLs into their rightful position? It was a left hook right hook.

My import plugin is WP Import Export Lite. Even the free version is fantastic.

I noticed WP Import Export offered to change the filenames of each URL-sourced image to whatever I wanted. So…

  1. I generated a column of filenames for each photo by concatenating certain cells in each row with “.jpg”.
  2. I planted the URL into the content field for each post and wrapped it in the HTML <img> tag.
  3. During the import,
    • WordPress Inserted from URL just as when we insert an image into a single post. It had no qualms because that was in the body of the post, not the Featured Image field.
    • WordPress saved each photo into my Media Library using the filenames I created. I now had 500 posts with images in the body and in the Media Library, but not the Featured Image.
  4. I re-imported the same spreadsheet with my filename column mapped to WordPress’s Featured Image field. During the second import, WordPress was able to find those files already in my Media Library. The Insert from URL was no longer necessary.

And, Voila! My 500 species are now in my Nature in Ponte Vedra blog with pictures that I can see at a glance while I customize them one at a time.

Let me summarize this for the WordPress folks.

To get images from URLs into the Featured Image field, import them first in the body of the post with known filenames so they’ll be added to the WordPress Media Library. Then re-import the posts with the filename column assigned to the Featured Image field.

Happy imaging.

Questions? Suggestions? Send me a note.

5 + 15 =