> ## Documentation Index
> Fetch the complete documentation index at: https://docs.megaport.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Megaport Location IDs

> This help topic lists Megaport location IDs and their details.

export const LocationsTable = () => {
  const API_URL = 'https://api.megaport.com/v3/locations';
  const HEADERS = [{
    text: 'ID',
    key: 'id'
  }, {
    text: 'Location Name',
    key: 'name'
  }, {
    text: 'City',
    key: 'city'
  }, {
    text: 'Metro',
    key: 'metro'
  }, {
    text: 'Country',
    key: 'country'
  }];
  const nameColumnWidth = key => {
    return key === 'name' ? 'w-2/5' : '';
  };
  const minColWidthClass = 'min-w-[105px]';
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [allLocations, setAllLocations] = React.useState([]);
  const [currentSort, setCurrentSort] = React.useState({
    column: 'id',
    ascending: true
  });
  const [searchTerm, setSearchTerm] = React.useState('');
  const [copiedId, setCopiedId] = React.useState(null);
  const [lastLoaded, setLastLoaded] = React.useState(null);
  const [retryKey, setRetryKey] = React.useState(0);
  React.useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    fetch(API_URL).then(r => {
      if (!r.ok) {
        throw new Error(`HTTP ${r.status}: ${r.statusText}`);
      }
      return r.json();
    }).then(data => {
      if (cancelled) {
        return;
      }
      setAllLocations((data.data || []).map(loc => ({
        id: loc.id,
        name: loc.name,
        metro: loc.metro || '',
        city: loc.address?.city || 'Unknown',
        country: loc.address?.country || 'Unknown'
      })).sort((a, b) => a.id - b.id));
      setLastLoaded(new Date());
      setLoading(false);
    }).catch(err => {
      if (cancelled) {
        return;
      }
      setError(err.message);
      setLoading(false);
    });
    return () => {
      cancelled = true;
    };
  }, [retryKey]);
  const filterLocations = (locations, term) => {
    const lower = term.toLowerCase();
    const noSpace = lower.replace(/\s+/g, '');
    if (!lower) {
      return locations;
    }
    return locations.filter(loc => {
      const text = `${loc.id} ${loc.name} ${loc.city} ${loc.metro} ${loc.country}`.toLowerCase();
      return text.includes(lower) || text.replace(/\s+/g, '').includes(noSpace);
    });
  };
  const searchInput = () => <div className="mb-5">
      <input type="text" placeholder="Search for city, location name, or ID..." className="w-full rounded border px-3 py-2 text-sm" aria-label="Search locations" value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
    </div>;
  const sortLocations = (locations, sort) => [...locations].sort((a, b) => {
    const key = sort.column;
    const aVal = key === 'id' ? a.id : a[key].toLowerCase();
    const bVal = key === 'id' ? b.id : b[key].toLowerCase();
    if (aVal < bVal) {
      return sort.ascending ? -1 : 1;
    }
    if (aVal > bVal) {
      return sort.ascending ? 1 : -1;
    }
    return 0;
  });
  const onSortClick = key => {
    setCurrentSort(prev => ({
      column: key,
      ascending: prev.column === key ? !prev.ascending : true
    }));
  };
  const sortableHeader = ({text, sortKey}) => {
    const isActive = currentSort.column === sortKey;
    return <th key={sortKey} scope="col" data-sort-key={sortKey} aria-sort={isActive ? currentSort.ascending ? 'ascending' : 'descending' : 'none'} className={`select-none px-3 py-2 text-left font-semibold ${minColWidthClass} ${nameColumnWidth(sortKey)}`}>
        <button type="button" className="flex w-full cursor-pointer items-center" onClick={() => onSortClick(sortKey)}>
          <span>{text}</span>
          <span className="ml-1 text-xs opacity-60" aria-hidden="true">
            {isActive ? currentSort.ascending ? '↑' : '↓' : '⇅'}
          </span>
        </button>
      </th>;
  };
  const onCopy = id => {
    if (!navigator.clipboard) {
      return;
    }
    navigator.clipboard.writeText(String(id)).then(() => {
      setCopiedId(id);
      setTimeout(() => setCopiedId(null), 1500);
    }).catch(() => {});
  };
  const locationRow = ({loc}) => <tr key={loc.id} data-id={loc.id}>
      <td className="px-3 py-2" style={{
    minWidth: 'max-content'
  }}>
        <div className="flex w-full items-center justify-end font-mono">
          <span>{loc.id}</span>
          <button type="button" className="inline-flex items-center opacity-50 hover:opacity-100 transition-opacity pl-3" aria-label={`Copy ID ${loc.id}`} title="Copy ID" onClick={() => onCopy(loc.id)}>
            <Icon icon={copiedId === loc.id ? 'check' : 'copy'} size={14} color={copiedId === loc.id ? '#22c55e' : '#E40046'} />
          </button>
        </div>
      </td>
      {HEADERS.slice(1).map(h => <td key={h.key} className={`px-3 py-2 ${minColWidthClass} ${nameColumnWidth(h.key)}`}>
          {loc[h.key]}
        </td>)}
    </tr>;
  const infoFooter = ({visible, total}) => <div className="mt-5 text-sm">
      <p className="block pb-5">
        <em>
          Showing {visible} of {total} locations
        </em>
      </p>
      <p className="block pb-5">
        <em>
          Data fetched from: <code>{API_URL}</code>
        </em>
      </p>
      {lastLoaded && <p className="block pb-5">
          <em>
            Last loaded: {lastLoaded.toLocaleString()} (UTC:{' '}
            {lastLoaded.toISOString().replace('T', ' ').substring(0, 19)})
          </em>
        </p>}
    </div>;
  const filtered = filterLocations(allLocations, searchTerm);
  const sorted = sortLocations(filtered, currentSort);
  return <div className="max-w-3xl">
      {searchInput()}
      {loading && <p className="py-5 text-center text-sm block">
          Loading locations data...
        </p>}
      {error && <div className="py-5 text-center">
          <div className="text-red-500">
            Failed to load locations data: {error}
          </div>
          <button type="button" className="mt-3 rounded px-4 py-2 text-sm text-white bg-[#E40046] hover:bg-[#c20039]" onClick={() => setRetryKey(k => k + 1)}>
            Retry
          </button>
        </div>}
      {!loading && !error && <>
          <table className="w-full text-sm">
            <caption className="sr-only">Megaport Location IDs</caption>
            <thead>
              <tr>
                {HEADERS.map(({text, key}) => sortableHeader({
    text,
    sortKey: key
  }))}
              </tr>
            </thead>
            <tbody>
              {sorted.length === 0 && searchTerm ? <tr>
                  <td colSpan={HEADERS.length} className="text-center py-5">
                    No locations found matching your search.
                  </td>
                </tr> : sorted.map(loc => locationRow({
    loc
  }))}
            </tbody>
          </table>
          {infoFooter({
    visible: sorted.length,
    total: allLocations.length
  })}
        </>}
    </div>;
};

<LocationsTable />

The table on this page is rendered dynamically using data from the Megaport Locations API. To retrieve the same data programmatically, send a `GET` request to `https://api.megaport.com/v3/locations` -- no authentication required. The response contains a `data` array where each element is a location object. The table surfaces five fields per location: `id` (integer, used in API requests), `name` (display name), `city` (from `address.city`), `metro` (metropolitan area), and `country` (from `address.country`).
