diff --git a/CHANGES.rst b/CHANGES.rst
index 1db2ee834..ae5bc9f2f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -23,7 +23,7 @@ datamodels
 - Fixed missing TUNITn keywords caused by changes for unsigned int columns. [#3753]
 
 - Write ``siaf_xref_sci`` and ``siaf_yref_sci`` to FITS keywords ``XREF_SCI``
-  and ``YREF_SCI`` for ``NRC_TSGRISM`` exposures. [#3766]
+  and ``YREF_SCI`` for ``NRC_TSGRISM`` exposures. [#3766
 
 extract_1d
 ----------
@@ -36,6 +36,11 @@ group_scale
 
 - Updates to documentation and log messages. [#3738]
 
+lib
+---
+
+- A function to determine the dispersion direction has been added. [#3756]
+
 stpipe
 ------
 
diff --git a/jwst/lib/dispaxis.py b/jwst/lib/dispaxis.py
new file mode 100644
index 000000000..74a255018
--- /dev/null
+++ b/jwst/lib/dispaxis.py
@@ -0,0 +1,152 @@
+import logging
+
+log = logging.getLogger(__name__)
+log.setLevel(logging.DEBUG)
+
+def get_dispersion_direction(exposure_type, grating="ANY", filter_wh="ANY",
+                             pupil="ANY"):
+    """Get the dispersion direction.
+
+    Parameters
+    ----------
+    exposure_type : str
+        The exposure type.
+
+    grating : str
+        The name of the optic in the grating wheel.
+
+    filter_wh : str
+        The name of the optic in the filter wheel.
+
+    pupil : str
+        The name of the optic in the pupil wheel.
+
+    Returns
+    -------
+    int or None : The dispersion direction
+
+-    None  the dispersion direction is not meaningful or not defined
+-       1  the dispersion direction is horizontal ("sky" coordinates)
+-       2  the dispersion direction is vertical ("sky" coordinates)
+    """
+
+    exposure_type = exposure_type.upper()
+    grating = grating.upper()
+    filter_wh = filter_wh.upper()
+    pupil = pupil.upper()
+
+    # The keys of the `by_exp_type` dictionary are values of exposure type.
+    # If the dispersion direction is uniquely determined by the exposure
+    # type, the associated value of by_exp_type[exposure_type] will be an
+    # integer, the dispersion direction.  If one or more parameters other
+    # than the exposure type are needed, the value of
+    # by_exp_type[exposure_type] will be a tuple of several strings; use
+    # that tuple as the key for dictionary `second_pass`, and the value of
+    # that will be the dispersion direction.
+    by_exp_type = {
+        # FGS science
+        "FGS_DARK": None,
+        "FGS_FOCUS": None,
+        "FGS_IMAGE": None,
+        "FGS_INTFLAT": None,
+        "FGS_SKYFLAT": None,
+        # FGS guide star
+        "FGS_ACQ1": None,
+        "FGS_ACQ2": None,
+        "FGS_FINEGUIDE": None,
+        "FGS_ID-IMAGE": None,
+        "FGS_ID-STACK": None,
+        "FGS_TRACK": None,
+        # MIRI
+        "MIR_4QPM": None,
+        "MIR_CORONCAL": None,
+        "MIR_DARKALL": None,
+        "MIR_DARKIMG": None,
+        "MIR_DARKMRS": None,
+        "MIR_FLATALL": None,
+        "MIR_FLATIMAGE": None,
+        "MIR_FLATIMAGE-EXT": None,
+        "MIR_FLATMRS": 2,
+        "MIR_FLATMRS-EXT": 2,
+        "MIR_IMAGE": None,
+        "MIR_LRS-FIXEDSLIT": 2,
+        "MIR_LRS-SLITLESS": 2,
+        "MIR_LYOT": None,
+        "MIR_MRS": 2,
+        "MIR_TACQ": None,
+        # NIRISS
+        "NIS_AMI": None,
+        "NIS_DARK": None,
+        "NIS_EXTCAL": None,
+        "NIS_FOCUS": None,
+        "NIS_IMAGE": None,
+        "NIS_LAMP": None,
+        "NIS_SOSS": 1,
+        "NIS_TACQ": None,
+        "NIS_TACONFIRM": None,
+        "NIS_WFSS": (exposure_type, "ANY", filter_wh, "ANY"),
+        # NIRCam
+        "NRC_CORON": None,
+        "NRC_DARK": None,
+        "NRC_FLAT": None,
+        "NRC_FOCUS": None,
+        "NRC_GRISM": (exposure_type, "ANY", "ANY", pupil),
+        "NRC_IMAGE": None,
+        "NRC_WFSS": (exposure_type, "ANY", "ANY", pupil),
+        "NRC_LED": None,
+        "NRC_WFSC": None,
+        "NRC_TACONFIRM": None,
+        "NRC_TACQ": None,
+        "NRC_TSGRISM": (exposure_type, "ANY", "ANY", pupil),
+        "NRC_TSIMAGE": None,
+        # NIRSpec
+        "NRS_AUTOFLAT": None,
+        "NRS_AUTOWAVE": 1,
+        "NRS_BRIGHTOBJ": 1,
+        "NRS_CONFIRM": None,
+        "NRS_DARK": None,
+        "NRS_FIXEDSLIT": 1,
+        "NRS_FOCUS": None,
+        "NRS_IFU": 1,
+        "NRS_IMAGE": None,
+        "NRS_LAMP":  1,
+        "NRS_MIMF": None,
+        "NRS_MSASPEC": 1,
+        "NRS_MSATA": None,
+        "NRS_TACONFIRM": None,
+        "NRS_TACQ": None,
+        "NRS_TASLIT": None,
+        "NRS_WATA": None,
+        # Misc
+        "N/A": None,
+        "ANY": None
+    }
+
+    if exposure_type not in by_exp_type.keys():
+        return None
+
+    # The strings in each tuple are exposure_type, grating, filter_wh, pupil.
+    second_pass = {
+        ("NIS_WFSS", "ANY", "GR150R", "ANY"): 2,
+        ("NIS_WFSS", "ANY", "GR150C", "ANY"): 1,
+
+        ("NRC_GRISM", "ANY", "ANY", "GRISMR"): 1,
+        ("NRC_GRISM", "ANY", "ANY", "GRISMC"): 2,
+
+        ("NRC_TSGRISM", "ANY", "ANY", "GRISMR"): 1,
+
+        ("NRC_WFSS", "ANY", "ANY", "GRISMR"): 1,
+        ("NRC_WFSS", "ANY", "ANY", "GRISMC"): 2
+    }
+
+    select = by_exp_type[exposure_type]
+    if isinstance(select, int):
+        return select
+    else:
+        if select in second_pass.keys():
+            return second_pass[select]
+        else:
+            log.warning("Error in get_dispersion_direction:  {} not in "
+                        "`second_pass`".format(select))
+            log.warning("Dispersion direction could not be determined.")
+            return None

Write a function to obtain the dispersion direction
Issue [JP-774](https://jira.stsci.edu/browse/JP-774) was created by Philip Hodge:

For spectroscopic data, there should be a function to determine the dispersion direction, based on instrument mode.
diff --git a/jwst/lib/tests/test_dispaxis.py b/jwst/lib/tests/test_dispaxis.py
new file mode 100644
index 000000000..17b328f27
--- /dev/null
+++ b/jwst/lib/tests/test_dispaxis.py
@@ -0,0 +1,85 @@
+"""
+Test for dispaxis
+"""
+
+from .. import dispaxis
+
+def test_dispaxis_1():
+    value = dispaxis.get_dispersion_direction("FGS_IMAGE", "junk", "junk",
+                                              "junk")
+    assert value == None
+
+def test_dispaxis_2():
+    value = dispaxis.get_dispersion_direction("MIR_4QPM", "junk", "junk",
+                                              "junk")
+    assert value == None
+
+def test_dispaxis_3():
+    value = dispaxis.get_dispersion_direction("MIR_LRS-SLITLESS", "junk",
+                                              "junk", "junk")
+    assert value == 2
+
+def test_dispaxis_4():
+    value = dispaxis.get_dispersion_direction("MIR_LRS-FIXEDSLIT", "junk",
+                                              "junk", "junk")
+    assert value == 2
+
+def test_dispaxis_5():
+    value = dispaxis.get_dispersion_direction("NIS_SOSS", "junk", "junk",
+                                              "junk")
+    assert value == 1
+
+def test_dispaxis_6():
+    value = dispaxis.get_dispersion_direction("NRS_FIXEDSLIT", "junk", "junk",
+                                              "junk")
+    assert value == 1
+
+def test_dispaxis_7():
+    value = dispaxis.get_dispersion_direction("NRS_IFU", "junk", "junk",
+                                              "junk")
+    assert value == 1
+
+def test_dispaxis_8():
+    value = dispaxis.get_dispersion_direction("NRS_MSASPEC", "junk", "junk",
+                                              "junk")
+    assert value == 1
+
+def test_dispaxis_9():
+    value = dispaxis.get_dispersion_direction("NIS_WFSS", "junk", "GR150R",
+                                              "junk")
+    assert value == 2
+
+def test_dispaxis_10():
+    value = dispaxis.get_dispersion_direction("NIS_WFSS", "junk", "GR150C",
+                                              "junk")
+    assert value == 1
+
+def test_dispaxis_11():
+    value = dispaxis.get_dispersion_direction("NRC_GRISM", "junk", "junk",
+                                              "GRISMR")
+    assert value == 1
+
+def test_dispaxis_12():
+    value = dispaxis.get_dispersion_direction("NRC_GRISM", "junk", "junk",
+                                              "GRISMC")
+    assert value == 2
+
+def test_dispaxis_13():
+    value = dispaxis.get_dispersion_direction("NRC_TSGRISM", "junk", "junk",
+                                              "GRISMR")
+    assert value == 1
+
+def test_dispaxis_14():
+    value = dispaxis.get_dispersion_direction("NRC_WFSS", "junk", "junk",
+                                              "GRISMR")
+    assert value == 1
+
+def test_dispaxis_15():
+    value = dispaxis.get_dispersion_direction("NRC_WFSS", "junk", "junk",
+                                              "GRISMC")
+    assert value == 2
+
+def test_dispaxis_16():
+    value = dispaxis.get_dispersion_direction("nrc_grism", "junk", "junk",
+                                              "missing")
+    assert value == None

pip install -e .[test]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y gcc
3.8
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
asdf==2.6.0
astropy==5.2.2
certifi==2025.1.31
charset-normalizer==3.4.1
ci-watson==0.6.1
coverage==7.6.1
crds==12.1.4
drizzle==1.14.4
exceptiongroup==1.2.2
execnet==2.1.1
filelock==3.16.1
gwcs==0.17.1
idna==3.10
iniconfig==2.1.0
jsonschema==2.6.0
-e git+https://github.com/spacetelescope/jwst.git@328d228e8e22b1ae584aa503708317be6dc573c1#egg=jwst
numpy==1.24.4
packaging==24.2
Parsley==1.3
photutils==1.8.0
pluggy==1.5.0
psutil==7.0.0
pyerfa==2.0.0.3
pytest==8.3.5
pytest-asyncio==0.24.0
pytest-cov==5.0.0
pytest-doctestplus==1.3.0
pytest-faulthandler==2.0.1
pytest-mock==3.14.0
pytest-openfiles==0.6.0
pytest-xdist==3.6.1
PyYAML==6.0.2
requests==2.32.3
requests-mock==1.12.1
scipy==1.10.1
semantic-version==2.10.0
spherical-geometry==1.2.23
stsci.image==2.3.5
stsci.imagestats==1.6.3
stsci.stimage==0.2.6
tomli==2.2.1
urllib3==2.2.3
verhawk==0.0.3

name: jwst
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=24.2=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - asdf==2.6.0
      - astropy==5.2.2
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - ci-watson==0.6.1
      - coverage==7.6.1
      - crds==12.1.4
      - drizzle==1.14.4
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - filelock==3.16.1
      - gwcs==0.17.1
      - idna==3.10
      - iniconfig==2.1.0
      - jsonschema==2.6.0
      - jwst==0.13.8a0.dev36+g328d228e
      - numpy==1.24.4
      - packaging==24.2
      - parsley==1.3
      - photutils==1.8.0
      - pluggy==1.5.0
      - psutil==7.0.0
      - pyerfa==2.0.0.3
      - pytest==8.3.5
      - pytest-asyncio==0.24.0
      - pytest-cov==5.0.0
      - pytest-doctestplus==1.3.0
      - pytest-faulthandler==2.0.1
      - pytest-mock==3.14.0
      - pytest-openfiles==0.6.0
      - pytest-xdist==3.6.1
      - pyyaml==6.0.2
      - requests==2.32.3
      - requests-mock==1.12.1
      - scipy==1.10.1
      - semantic-version==2.10.0
      - spherical-geometry==1.2.23
      - stsci-image==2.3.5
      - stsci-imagestats==1.6.3
      - stsci-stimage==0.2.6
      - tomli==2.2.1
      - urllib3==2.2.3
      - verhawk==0.0.3
prefix: /opt/conda/envs/jwst

jwst/lib/tests/test_dispaxis.py::test_dispaxis_1 jwst/lib/tests/test_dispaxis.py::test_dispaxis_2 jwst/lib/tests/test_dispaxis.py::test_dispaxis_3 jwst/lib/tests/test_dispaxis.py::test_dispaxis_4 jwst/lib/tests/test_dispaxis.py::test_dispaxis_5 jwst/lib/tests/test_dispaxis.py::test_dispaxis_6 jwst/lib/tests/test_dispaxis.py::test_dispaxis_7 jwst/lib/tests/test_dispaxis.py::test_dispaxis_8 jwst/lib/tests/test_dispaxis.py::test_dispaxis_9 jwst/lib/tests/test_dispaxis.py::test_dispaxis_10 jwst/lib/tests/test_dispaxis.py::test_dispaxis_11 jwst/lib/tests/test_dispaxis.py::test_dispaxis_12 jwst/lib/tests/test_dispaxis.py::test_dispaxis_13 jwst/lib/tests/test_dispaxis.py::test_dispaxis_14 jwst/lib/tests/test_dispaxis.py::test_dispaxis_15 jwst/lib/tests/test_dispaxis.py::test_dispaxis_16
jwst/lib/dispaxis.py CHANGES.rst
jwst/lib/dispaxis.py CHANGES.rst