Journal.Index
Academic Journal Discovery via ISSN
Try: 0028-0836 (Nature) 1476-4687 (Nature e-ISSN) 0036-8075 (Science) 0140-6736 (The Lancet)
Querying CrossRef & OpenAlex APIs…
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

// Set date
  document.getElementById('dateStamp').textContent = new Date().toLocaleDateString('en-GB', {
    day:'2-digit', month:'short', year:'numeric'
  }).toUpperCase();

  // ISSN format on input
  document.getElementById('issnInput').addEventListener('input', function(e) {
    let v = e.target.value.replace(/[^0-9Xx]/g, '');
    if (v.length > 4) v = v.slice(0,4) + '-' + v.slice(4,8);
    e.target.value = v;
  });

  document.getElementById('issnInput').addEventListener('keydown', function(e) {
    if (e.key === 'Enter') search();
  });

  function fillAndSearch(issn) {
    document.getElementById('issnInput').value = issn;
    search();
  }

  function showLoader(v) {
    document.getElementById('loader').style.display = v ? 'block' : 'none';
  }
  function showError(msg) {
    document.getElementById('error').style.display = msg ? 'block' : 'none';
    document.getElementById('errorMsg').textContent = msg || '';
  }
  function clearResults() {
    document.getElementById('results').innerHTML = '';
  }

  async function search() {
    const raw = document.getElementById('issnInput').value.trim();
    const issn = raw.replace(/[^0-9Xx-]/g,'').toUpperCase();
    if (!/^\d{4}-[\dX]{4}$/i.test(issn)) {
      showError('⚠ Invalid ISSN format. Please enter in the format XXXX-XXXX (e.g. 0028-0836).');
      clearResults();
      return;
    }

    showError('');
    clearResults();
    showLoader(true);

    try {
      const [crossrefData, openAlexData, articlesData] = await Promise.allSettled([
        fetchCrossRef(issn),
        fetchOpenAlex(issn),
        fetchRecentArticles(issn)
      ]);

      showLoader(false);

      const cr = crossrefData.status === 'fulfilled' ? crossrefData.value : null;
      const oa = openAlexData.status === 'fulfilled' ? openAlexData.value : null;
      const arts = articlesData.status === 'fulfilled' ? articlesData.value : [];

      if (!cr && !oa) {
        showError(`✗ No journal found for ISSN ${issn}. Please verify the number and try again.`);
        return;
      }

      renderResults(issn, cr, oa, arts);
    } catch(e) {
      showLoader(false);
      showError('⚠ Network error: ' + e.message);
    }
  }

  async function fetchCrossRef(issn) {
    const res = await fetch(`https://api.crossref.org/journals/${issn}`);
    if (!res.ok) return null;
    const j = await res.json();
    return j.message || null;
  }

  async function fetchOpenAlex(issn) {
    const res = await fetch(`https://api.openalex.org/sources?filter=issn:${issn}&per_page=1`);
    if (!res.ok) return null;
    const j = await res.json();
    return j.results && j.results.length > 0 ? j.results[0] : null;
  }

  async function fetchRecentArticles(issn) {
    const res = await fetch(`https://api.crossref.org/journals/${issn}/works?rows=8&sort=published&order=desc`);
    if (!res.ok) return [];
    const j = await res.json();
    return (j.message && j.message.items) ? j.message.items : [];
  }

  function val(v, fallback = '—') {
    if (v === undefined || v === null || v === '') return fallback;
    return v;
  }

  function renderResults(issn, cr, oa, articles) {
    const container = document.getElementById('results');
    let html = '';

    // ── Journal metadata card ──
    const title = (cr && cr.title) || (oa && oa.display_name) || issn;
    const publisher = (cr && cr.publisher) || (oa && oa.host_organization_name) || '—';
    const subjects = (cr && cr.subjects && cr.subjects.length)
      ? cr.subjects.map(s => s.name).join(', ')
      : (oa && oa.topics && oa.topics.length ? oa.topics.slice(0,4).map(t=>t.display_name).join(', ') : '—');
    const homepage = (cr && cr['URL']) || (oa && oa.homepage_url) || null;
    const totalWorks = (cr && cr.counts && cr.counts['total-dois']) 
      ? cr.counts['total-dois'].toLocaleString()
      : (oa && oa.works_count ? oa.works_count.toLocaleString() : '—');
    const citedByCount = (oa && oa.cited_by_count) ? oa.cited_by_count.toLocaleString() : '—';
    const isOpenAccess = (oa && oa.is_oa) ? 'Yes' : (oa ? 'No' : '—');
    const country = (oa && oa.country_code) ? oa.country_code : '—';
    const type = (oa && oa.type) ? oa.type.replace(/-/g,' ') : (cr && cr.type ? cr.type : '—');

    // ISSN badges
    let issnBadges = `<span class="issn-badge">${issn}</span>`;
    if (cr && cr.ISSN && cr.ISSN.length > 1) {
      cr.ISSN.forEach((i,idx) => {
        if (i !== issn) issnBadges += `<span class="issn-badge electronic">${i}</span>`;
      });
    }
    if (cr && cr['issn-type']) {
      issnBadges = '';
      cr['issn-type'].forEach(it => {
        const cls = it.type === 'print' ? 'print' : 'electronic';
        issnBadges += `<span class="issn-badge ${cls}" title="${it.type}">${it.value} <small style="opacity:.6;font-size:.9em">${it.type.charAt(0).toUpperCase()}</small></span>`;
      });
    }

    // Sources used
    const sources = [];
    if (cr) sources.push('CrossRef');
    if (oa) sources.push('OpenAlex');

    html += `
    <div class="journal-card">
      <div class="card-header">
        <div class="journal-title">${escHtml(title)}</div>
        <div style="margin-top:.6rem">${issnBadges}</div>
      </div>
      <div class="card-body">
        <div class="info-item">
          <div class="info-key">Publisher</div>
          <div class="info-val">${escHtml(publisher)}</div>
        </div>
        <div class="info-item">
          <div class="info-key">Type</div>
          <div class="info-val" style="text-transform:capitalize">${escHtml(type)}</div>
        </div>
        <div class="info-item">
          <div class="info-key">Country</div>
          <div class="info-val">${escHtml(country)}</div>
        </div>
        <div class="info-item">
          <div class="info-key">Open Access</div>
          <div class="info-val">${isOpenAccess}</div>
        </div>
        <div class="info-item">
          <div class="info-key">Total Works</div>
          <div class="info-val">${totalWorks}</div>
        </div>
        <div class="info-item">
          <div class="info-key">Cited By</div>
          <div class="info-val">${citedByCount}</div>
        </div>
        ${subjects !== '—' ? `
        <div class="info-item" style="grid-column:1/-1">
          <div class="info-key">Subject Areas</div>
          <div class="info-val">${escHtml(subjects)}</div>
        </div>` : ''}
        ${homepage ? `
        <div class="info-item" style="grid-column:1/-1">
          <div class="info-key">Homepage</div>
          <div class="info-val"><a href="${escHtml(homepage)}" target="_blank" rel="noopener">${escHtml(homepage)}</a></div>
        </div>` : ''}
      </div>
      <div class="source-strip">
        <span class="source-label">Data from:</span>
        ${sources.map(s=>`<span class="source-tag">${s}</span>`).join('')}
      </div>
    </div>`;

    // ── Recent Articles ──
    if (articles && articles.length > 0) {
      html += `<div class="section-divider"><span>Recent Articles (CrossRef)</span></div>`;
      html += `<div class="articles-section">
        <div class="articles-header">
          <span>Latest Publications</span>
          <span class="articles-count">${articles.length} results</span>
        </div>`;
      articles.forEach(art => {
        const artTitle = (art.title && art.title.length) ? art.title[0] : 'Untitled';
        const authors = (art.author && art.author.length)
          ? art.author.slice(0,3).map(a => a.family ? `${a.given ? a.given[0]+'. ' : ''}${a.family}` : '').filter(Boolean).join(', ') + (art.author.length > 3 ? ' et al.' : '')
          : '';
        const year = art.published && art.published['date-parts'] && art.published['date-parts'][0]
          ? art.published['date-parts'][0][0]
          : (art.created && art.created['date-parts'] ? art.created['date-parts'][0][0] : '');
        const doi = art.DOI || '';
        const volume = art.volume ? `Vol. ${art.volume}` : '';
        const issue = art.issue ? `Issue ${art.issue}` : '';
        const pages = art.page ? `pp. ${art.page}` : '';
        const meta = [volume, issue, pages].filter(Boolean).join(' · ');

        html += `<div class="article-row">
          <div class="article-doi-row">
            <div class="article-title">${escHtml(artTitle)}</div>
            ${year ? `<div class="article-year">${year}</div>` : ''}
          </div>
          <div class="article-meta">
            ${authors ? `<span>${escHtml(authors)}</span>` : ''}
            ${meta ? `<span style="margin:0 .4rem;opacity:.5">·</span><span>${escHtml(meta)}</span>` : ''}
            ${doi ? `<span style="margin:0 .4rem;opacity:.5">·</span><a href="https://doi.org/${doi}" target="_blank" rel="noopener">DOI: ${escHtml(doi)}</a>` : ''}
          </div>
        </div>`;
      });
      html += `</div>`;
    }

    container.innerHTML = html;
  }

  function escHtml(str) {
    if (!str) return '';
    return String(str)
      .replace(/&/g,'&amp;')
      .replace(/</g,'&lt;')
      .replace(/>/g,'&gt;')
      .replace(/"/g,'&quot;');
  }