JSON to Google Sheets

Paste or drop JSON, copy a sheet-ready table, paste it into cell A1. Or keep a sheet live from a JSON URL.

Paste anywhere on this page, or drop a .json, .jsonl, or .csv file.

Keep a Sheet Live From a URL

Pull JSON from a URL straight into a sheet with one formula. It runs inside your own Google account, so an API's data never passes through us.

  1. Open the script editor. In your Google Sheet, choose Extensions → Apps Script.
  2. Paste the script. Replace everything in Code.gs with the script below, then click Save.
  3. Approve it once. With authorize selected in the toolbar, click Run, then Review permissions and allow access. Google may say the app isn’t verified: it’s your own script, so choose Advanced, then Go to the project. Formulas can’t ask for this permission themselves, so this step is needed once.
  4. Use the formula. Back in the sheet, type it into any cell: =IMPORTJSON("https://api.example.com/users")
Code.gs
/**
 * Run this once from the editor (select authorize, click Run) and approve
 * access. Formulas in cells can't ask for permission on their own.
 * It only builds a request; nothing is fetched.
 */
function authorize() {
  UrlFetchApp.getRequest('https://example.com');
}

/**
 * Imports JSON from a URL into your sheet as a table: one row per
 * record, nested fields as dot-notation columns (Address.City).
 * From jsonsupport.com.
 *
 * @param {string} url      The URL that returns JSON.
 * @param {string} path     Optional. Where the records are: "data.items".
 * @param {any}    refresh  Optional. Point this at any cell; changing
 *                          that cell re-fetches the data.
 * @return The table, header row first.
 * @customfunction
 */
function IMPORTJSON(url, path, refresh) {
  if (!url) {
    throw new Error('Give IMPORTJSON a URL: =IMPORTJSON("https://...")');
  }

  const response = UrlFetchApp.fetch(url, {
    muteHttpExceptions: true,
    headers: { Accept: 'application/json' },
  });
  const status = response.getResponseCode();
  if (status >= 400) throw new Error('The URL returned HTTP ' + status + '.');

  let data;
  try {
    data = JSON.parse(response.getContentText());
  } catch (e) {
    throw new Error('The URL did not return valid JSON.');
  }

  if (path) {
    for (const key of String(path).split('.')) {
      if (data === null || typeof data !== 'object' || !(key in data)) {
        throw new Error('"' + path + '" was not found in the response.');
      }
      data = data[key];
    }
  } else if (isObject_(data) && !Array.isArray(data)) {
    // No path given: APIs often wrap the records, e.g. {"data": [...]}.
    const lists = Object.keys(data).filter(key =>
      Array.isArray(data[key]) && data[key].some(isObject_));
    if (lists.length) {
      lists.sort((a, b) => data[b].length - data[a].length);
      data = data[lists[0]];
    }
  }

  const records = Array.isArray(data) ? data : [data];
  if (records.length === 0) return [['No records']];

  const flat = records.map(record => flatten_(record, ''));
  const keys = [];
  const seen = {};
  flat.forEach(row => Object.keys(row).forEach(key => {
    if (!seen[key]) { seen[key] = true; keys.push(key); }
  }));

  const header = keys.map(key => key.split('.')
    .map(part => part.charAt(0).toUpperCase() + part.slice(1))
    .join('.'));
  const rows = flat.map(row => keys.map(key =>
    (row[key] === undefined || row[key] === null ? '' : row[key])));
  return [header].concat(rows);
}

function isObject_(value) {
  return value !== null && typeof value === 'object';
}

function flatten_(value, prefix) {
  if (!isObject_(value)) {
    const single = {};
    single[prefix || 'value'] = value;
    return single;
  }
  const out = {};
  Object.keys(value).forEach(key => {
    const flatKey = prefix ? prefix + '.' + key : key;
    const child = value[key];
    if (Array.isArray(child)) {
      if (!child.some(isObject_)) {
        // A simple list (tags, ids) becomes one comma-separated cell.
        out[flatKey] = child
          .map(item => (item === null ? '' : String(item)))
          .join(', ');
      } else {
        child.forEach((item, i) =>
          Object.assign(out, flatten_(item, flatKey + '.' + i)));
      }
    } else if (isObject_(child)) {
      Object.assign(out, flatten_(child, flatKey));
    } else {
      out[flatKey] = child === null ? '' : child;
    }
  });
  return out;
}

More ways to call it

=IMPORTJSON("https://…", "data.items")
Records sit deeper in the response? Name the path. Without one, a wrapper like {"data": [...]} is found automatically.
=IMPORTJSON(A1)
Keep the URL in a cell. Change the cell and the data reloads.
=IMPORTJSON("https://…", , B1)
Refresh on demand: point the third argument at a checkbox and tick it to re-fetch. Sheets doesn't allow NOW() here.

Building this into your own product?

Call the same conversion from your own code with a rate-limited developer API: a free tier to start, Pro for production traffic.

View the Developer API

How to Put JSON Into Google Sheets

  1. Paste or drop your JSON. An array of records, an API response, or JSON Lines all work. Wrapped responses like {"data": [...]} are unwrapped for you.
  2. Check the table. It appears instantly, with the exact rows and columns Sheets will get, with nested fields flattened into columns like Address.City.
  3. Copy, then paste into A1. Click Copy for Google Sheets, open your sheet, select cell A1, and paste with Ctrl+V (⌘V on a Mac; on a phone, tap A1 twice and choose Paste). Every value lands in its own cell.

Prefer a file? Download CSV and use File → Import → Upload in Google Sheets. Copying and the CSV are built in your browser, so that data never leaves your device. Only Download Excel sends the rows to our converter, which builds the file and doesn't save your data.

Questions

Can Google Sheets open a JSON file directly?

No. Sheets imports CSV, TSV, and Excel files, not JSON. Convert it to a flat table first: copy the result above and paste it, or download the CSV and import that.

How are nested objects and arrays handled?

Nested objects become dot-notation columns (Address.City). Arrays of objects, like orders, become one row per item or numbered columns (Orders.0.Sku, Orders.1.Sku), your choice, and simple lists like tags become one comma-separated cell. It's the same logic as the JSON to Excel converter, so both give you identical columns; see both layouts side by side.

Will Sheets change any of my values?

Copy for Google Sheets marks text that Sheets would otherwise rewrite, such as IDs with leading zeros (00123) or text starting with =, so it's kept as text. Real JSON numbers stay numbers you can sum and sort. If you import the CSV instead and need leading zeros, untick "Convert text to numbers, dates, and formulas" in the import dialog.

Does IMPORTJSON work with APIs that need a key?

Yes, if the API accepts the key in the URL, for example ?api_key=…. Keep in mind that anyone who can edit the sheet can see a key written into a formula.

For a longer walkthrough of every method, read how to import JSON into Google Sheets. Working with JSON Lines? See what NDJSON is.