diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2046c8e..409e547b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # jsonld ChangeLog
 
+## 8.x.x - 2023-xx-xx
+
+### Added
+- Support `compound-literal` `rdfDirection` option.
+
 ## 8.3.0 - 2023-09-06
 
 ### Added
diff --git a/lib/fromRdf.js b/lib/fromRdf.js
index 01098353..518b4e8c 100644
--- a/lib/fromRdf.js
+++ b/lib/fromRdf.js
@@ -18,7 +18,7 @@ const {
 
 // constants
 const {
-  // RDF,
+  RDF,
   RDF_LIST,
   RDF_FIRST,
   RDF_REST,
@@ -61,12 +61,10 @@ api.fromRDF = async (
   const defaultGraph = {};
   const graphMap = {'@default': defaultGraph};
   const referencedOnce = {};
+  let processCompoundLiterals = false;
   if(rdfDirection) {
     if(rdfDirection === 'compound-literal') {
-      throw new JsonLdError(
-        'Unsupported rdfDirection value.',
-        'jsonld.InvalidRdfDirection',
-        {value: rdfDirection});
+      processCompoundLiterals = true;
     } else if(rdfDirection !== 'i18n-datatype') {
       throw new JsonLdError(
         'Unknown rdfDirection value.',
@@ -74,6 +72,10 @@ api.fromRDF = async (
         {value: rdfDirection});
     }
   }
+  let compoundLiteralSubjects;
+  if(processCompoundLiterals) {
+    compoundLiteralSubjects = {};
+  }
 
   for(const quad of dataset) {
     // TODO: change 'name' to 'graph'
@@ -82,11 +84,18 @@ api.fromRDF = async (
     if(!(name in graphMap)) {
       graphMap[name] = {};
     }
+    if(processCompoundLiterals && !(name in compoundLiteralSubjects)) {
+      compoundLiteralSubjects[name] = {};
+    }
     if(name !== '@default' && !(name in defaultGraph)) {
       defaultGraph[name] = {'@id': name};
     }
 
     const nodeMap = graphMap[name];
+    let compoundMap;
+    if(processCompoundLiterals) {
+      compoundMap = compoundLiteralSubjects[name];
+    }
 
     // get subject, predicate, object
     const s = quad.subject.value;
@@ -97,6 +106,9 @@ api.fromRDF = async (
       nodeMap[s] = {'@id': s};
     }
     const node = nodeMap[s];
+    if(processCompoundLiterals && p === RDF + 'direction') {
+      compoundMap[s] = true;
+    }
 
     const objectIsNode = o.termType.endsWith('Node');
     if(objectIsNode && !(o.value in nodeMap)) {
@@ -208,6 +220,64 @@ api.fromRDF = async (
   for(const name in graphMap) {
     const graphObject = graphMap[name];
 
+    if(processCompoundLiterals) {
+      if(name in compoundLiteralSubjects) {
+        const cls = compoundLiteralSubjects[name];
+        for(const cl of Object.keys(cls)) {
+          const clEntry = referencedOnce[cl];
+          if(!clEntry) {
+            continue;
+          }
+          const node = clEntry.node;
+          const property = clEntry.property;
+          //const value = clEntry.value;
+          const clNode = graphObject[cl];
+          if(!types.isObject(clNode)) {
+            continue;
+          }
+          delete graphObject[cl];
+          for(const clReference of node[property]) {
+            if(clReference['@id'] === cl) {
+              delete clReference['@id'];
+            }
+            const value = clNode[RDF + 'value'];
+            // FIXME: error on !== 1 value
+            clReference['@value'] = value[0]['@value'];
+            const language = clNode[RDF + 'language'];
+            if(language) {
+              // FIXME: error on !== 1 language value
+              const v = language[0]['@value'];
+              if(!v.match(REGEX_BCP47)) {
+                throw new JsonLdError(
+                  'Invalid RDF syntax; rdf:language must be valid BCP47.',
+                  'jsonld.SyntaxError',
+                  {
+                    code: 'invalid language-tagged string',
+                    value: v
+                  });
+              }
+              clReference['@language'] = v;
+            }
+            const direction = clNode[RDF + 'direction'];
+            if(direction) {
+              // FIXME: error on !== 1 direction value
+              const v = direction[0]['@value'];
+              if(!(v === 'ltr' || v === 'rtl')) {
+                throw new JsonLdError(
+                  'Invalid RDF syntax; rdf:direction must be "ltr" or "rtl".',
+                  'jsonld.SyntaxError',
+                  {
+                    code: 'invalid base direction',
+                    value: v
+                  });
+              }
+              clReference['@direction'] = v;
+            }
+          }
+        }
+      }
+    }
+
     // no @lists to be converted, continue
     if(!(RDF_NIL in graphObject)) {
       continue;
@@ -296,7 +366,8 @@ api.fromRDF = async (
  *
  * @param o the RDF triple object to convert.
  * @param useNativeTypes true to output native types, false not to.
- * @param rdfDirection text direction mode [null, i18n-datatype]
+ * @param rdfDirection text direction mode [null, i18n-datatype,
+ *   compound-literal]
  * @param options top level API options
  *
  * @return the JSON-LD object.
diff --git a/lib/jsonld.js b/lib/jsonld.js
index c6931aeb..cd5deb73 100644
--- a/lib/jsonld.js
+++ b/lib/jsonld.js
@@ -547,7 +547,8 @@ jsonld.link = async function(input, ctx, options) {
  *            'application/n-quads' for N-Quads.
  *          [documentLoader(url, options)] the document loader.
  *          [useNative] true to use a native canonize algorithm
- *          [rdfDirection] null or 'i18n-datatype' to support RDF
+ *          [rdfDirection] Mode for RDF transformation of @direction. null,
+ *            'i18n-datatype', or 'compound-literal' (default: null).
  *             transformation of @direction (default: null).
  *          [safe] true to use safe mode. (default: true).
  *          [contextResolver] internal use only.
@@ -605,7 +606,8 @@ jsonld.normalize = jsonld.canonize = async function(input, options) {
  *            (default: false).
  *          [useNativeTypes] true to convert XSD types into native types
  *            (boolean, integer, double), false not to (default: false).
- *          [rdfDirection] null or 'i18n-datatype' to support RDF
+ *          [rdfDirection] Mode for RDF transformation of @direction. null,
+ *            'i18n-datatype', or 'compound-literal' (default: null).
  *             transformation of @direction (default: null).
  *          [safe] true to use safe mode. (default: false)
  *
@@ -659,7 +661,8 @@ jsonld.fromRDF = async function(dataset, options) {
  *            to produce only standard RDF (default: false).
  *          [documentLoader(url, options)] the document loader.
  *          [safe] true to use safe mode. (default: false)
- *          [rdfDirection] null or 'i18n-datatype' to support RDF
+ *          [rdfDirection] Mode for RDF transformation of @direction. null,
+ *            'i18n-datatype', or 'compound-literal' (default: null).
  *             transformation of @direction (default: null).
  *          [contextResolver] internal use only.
  *
diff --git a/lib/toRdf.js b/lib/toRdf.js
index f576f6d8..7c960f76 100644
--- a/lib/toRdf.js
+++ b/lib/toRdf.js
@@ -16,7 +16,7 @@ const {
 } = require('./events');
 
 const {
-  // RDF,
+  RDF,
   // RDF_LIST,
   RDF_FIRST,
   RDF_REST,
@@ -320,10 +320,74 @@ function _objectToRDF(
       object.datatype.value = datatype;
       object.value = value;
     } else if('@direction' in item && rdfDirection === 'compound-literal') {
-      throw new JsonLdError(
-        'Unsupported rdfDirection value.',
-        'jsonld.InvalidRdfDirection',
-        {value: rdfDirection});
+      const language = (item['@language'] || '').toLowerCase();
+      const direction = item['@direction'];
+      // blank node
+      object.termType = 'BlankNode';
+      object.value = issuer.getId();
+      object.datatype = undefined;
+      // value
+      dataset.push({
+        subject: {
+          termType: object.termType,
+          value: object.value
+        },
+        predicate: {
+          termType: 'NamedNode',
+          value: RDF + 'value'
+        },
+        object: {
+          termType: 'Literal',
+          value,
+          datatype: {
+            termType: 'NamedNode',
+            value: XSD_STRING
+          }
+        },
+        graph: graphTerm
+      });
+      // language if preset
+      if(language !== '') {
+        dataset.push({
+          subject: {
+            termType: object.termType,
+            value: object.value
+          },
+          predicate: {
+            termType: 'NamedNode',
+            value: RDF + 'language'
+          },
+          object: {
+            termType: 'Literal',
+            value: language,
+            datatype: {
+              termType: 'NamedNode',
+              value: XSD_STRING
+            }
+          },
+          graph: graphTerm
+        });
+      }
+      // direction
+      dataset.push({
+        subject: {
+          termType: object.termType,
+          value: object.value
+        },
+        predicate: {
+          termType: 'NamedNode',
+          value: RDF + 'direction'
+        },
+        object: {
+          termType: 'Literal',
+          value: direction,
+          datatype: {
+            termType: 'NamedNode',
+            value: XSD_STRING
+          }
+        },
+        graph: graphTerm
+      });
     } else if('@direction' in item && rdfDirection) {
       throw new JsonLdError(
         'Unknown rdfDirection value.',
diff --git a/tests/misc.js b/tests/misc.js
index a552d7f9..a0c1a559 100644
--- a/tests/misc.js
+++ b/tests/misc.js
@@ -3695,6 +3695,17 @@ _:b0 <ex:p> "[null]"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON> .
 `;
   const _nq_dir_l_d_i18n = `\
 <urn:id> <ex:p> "v"^^<https://www.w3.org/ns/i18n#en-us_ltr> .
+`;
+  const _nq_dir_nl_d_cl = `\
+<urn:id> <ex:p> _:b0 .
+_:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#direction> "ltr" .
+_:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#value> "v" .
+`;
+  const _nq_dir_l_d_cl = `\
+<urn:id> <ex:p> _:b0 .
+_:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#direction> "ltr" .
+_:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#language> "en-us" .
+_:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#value> "v" .
 `;
 
   describe('fromRDF', () => {
@@ -3811,6 +3822,20 @@ _:b0 <ex:p> "[null]"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON> .
       });
     });
 
+    it('should handle no @lang, no @dir, rdfDirection=c-l', async () => {
+      const input = _nq_dir_nl_nd;
+      const expected = _json_dir_nl_nd;
+
+      await _test({
+        type: 'fromRDF',
+        input,
+        options: {skipExpansion: true, rdfDirection: 'compound-literal'},
+        expected,
+        eventCodeLog: [],
+        testSafe: true
+      });
+    });
+
     it('should handle no @lang, @dir, rdfDirection=i18n', async () => {
       const input = _nq_dir_nl_d_i18n;
       const expected = _json_dir_nl_d;
@@ -3825,6 +3850,20 @@ _:b0 <ex:p> "[null]"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON> .
       });
     });
 
+    it('should handle no @lang, @dir, rdfDirection=c-l', async () => {
+      const input = _nq_dir_nl_d_cl;
+      const expected = _json_dir_nl_d;
+
+      await _test({
+        type: 'fromRDF',
+        input,
+        options: {skipExpansion: true, rdfDirection: 'compound-literal'},
+        expected,
+        eventCodeLog: [],
+        testSafe: true
+      });
+    });
+
     it('should handle @lang, no @dir, rdfDirection=i18n', async () => {
       const input = _nq_dir_l_nd_ls;
       const expected = _json_dir_l_nd;
@@ -3839,6 +3878,20 @@ _:b0 <ex:p> "[null]"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON> .
       });
     });
 
+    it('should handle @lang, no @dir, rdfDirection=c-l', async () => {
+      const input = _nq_dir_l_nd_ls;
+      const expected = _json_dir_l_nd;
+
+      await _test({
+        type: 'fromRDF',
+        input,
+        options: {skipExpansion: true, rdfDirection: 'compound-literal'},
+        expected,
+        eventCodeLog: [],
+        testSafe: true
+      });
+    });
+
     it('should handle @lang, @dir, rdfDirection=i18n', async () => {
       const input = _nq_dir_l_d_i18n;
       const expected = _json_dir_l_d;
@@ -3853,6 +3906,20 @@ _:b0 <ex:p> "[null]"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON> .
       });
     });
 
+    it('should handle @lang, @dir, rdfDirection=c-l', async () => {
+      const input = _nq_dir_l_d_cl;
+      const expected = _json_dir_l_d;
+
+      await _test({
+        type: 'fromRDF',
+        input,
+        options: {skipExpansion: true, rdfDirection: 'compound-literal'},
+        expected,
+        eventCodeLog: [],
+        testSafe: true
+      });
+    });
+
     it('should handle bad rdfDirection', async () => {
       const input = _nq_dir_l_d_i18n;
 
@@ -4101,6 +4168,20 @@ _:b0 <ex:p> "v" .
       });
     });
 
+    it('should handle no @lang, no @dir, rdfDirection=c-l', async () => {
+      const input = _json_dir_nl_nd;
+      const nq = _nq_dir_nl_nd;
+
+      await _test({
+        type: 'toRDF',
+        input,
+        options: {skipExpansion: true, rdfDirection: 'compound-literal'},
+        expected: nq,
+        eventCodeLog: [],
+        testSafe: true
+      });
+    });
+
     it('should handle no @lang, @dir, rdfDirection=null', async () => {
       const input = _json_dir_nl_d;
       const nq = _nq_dir_nl_nd;
@@ -4131,6 +4212,20 @@ _:b0 <ex:p> "v" .
       });
     });
 
+    it('should handle no @lang, @dir, rdfDirection=c-l', async () => {
+      const input = _json_dir_nl_d;
+      const nq = _nq_dir_nl_d_cl;
+
+      await _test({
+        type: 'toRDF',
+        input,
+        options: {skipExpansion: true, rdfDirection: 'compound-literal'},
+        expected: nq,
+        eventCodeLog: [],
+        testSafe: true
+      });
+    });
+
     it('should handle @lang, no @dir, rdfDirection=null', async () => {
       const input = _json_dir_l_nd;
       const nq = _nq_dir_l_nd_ls;
@@ -4159,6 +4254,20 @@ _:b0 <ex:p> "v" .
       });
     });
 
+    it('should handle @lang, no @dir, rdfDirection=c-l', async () => {
+      const input = _json_dir_l_nd;
+      const nq = _nq_dir_l_nd_ls;
+
+      await _test({
+        type: 'toRDF',
+        input,
+        options: {skipExpansion: true, rdfDirection: 'compound-literal'},
+        expected: nq,
+        eventCodeLog: [],
+        testSafe: true
+      });
+    });
+
     it('should handle @lang, @dir, rdfDirection=null', async () => {
       const input = _json_dir_l_d;
       const nq = _nq_dir_l_nd_ls;
@@ -4189,6 +4298,20 @@ _:b0 <ex:p> "v" .
       });
     });
 
+    it('should handle @lang, @dir, rdfDirection=c-l', async () => {
+      const input = _json_dir_l_d;
+      const nq = _nq_dir_l_d_cl;
+
+      await _test({
+        type: 'toRDF',
+        input,
+        options: {skipExpansion: true, rdfDirection: 'compound-literal'},
+        expected: nq,
+        eventCodeLog: [],
+        testSafe: true
+      });
+    });
+
     it('should handle bad rdfDirection', async () => {
       const input = _json_dir_l_d;
 
@@ -4258,6 +4381,30 @@ _:b0 <urn:ex:title> "RTL"^^<https://www.w3.org/ns/i18n#ar-eg_rtl> .
         testSafe: true
       });
     });
+
+    it('should handle ctx @lang/@dir/rdfDirection=c-l', async () => {
+      const input = _ctx_dir_input;
+      const nq = `\
+_:b0 <urn:ex:publisher> "NULL"@ar-eg .
+_:b0 <urn:ex:title> _:b1 .
+_:b0 <urn:ex:title> _:b2 .
+_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#direction> "rtl" .
+_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#language> "ar-eg" .
+_:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#value> "RTL" .
+_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#direction> "ltr" .
+_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#language> "en" .
+_:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#value> "LTR" .
+`;
+
+      await _test({
+        type: 'toRDF',
+        input,
+        options: {skipExpansion: false, rdfDirection: 'compound-literal'},
+        expected: nq,
+        eventCodeLog: [],
+        testSafe: true
+      });
+    });
   });
 
   describe('various', () => {
diff --git a/tests/test.js b/tests/test.js
index a0af0eba..c463bb64 100644
--- a/tests/test.js
+++ b/tests/test.js
@@ -264,13 +264,7 @@ const TEST_TYPES = {
       // NOTE: idRegex format:
       // /MMM-manifest#tNNN$/,
       // FIXME
-      idRegex: [
-        // direction (compound-literal)
-        /fromRdf-manifest#tdi09$/,
-        /fromRdf-manifest#tdi10$/,
-        /fromRdf-manifest#tdi11$/,
-        /fromRdf-manifest#tdi12$/,
-      ]
+      idRegex: []
     },
     fn: 'fromRDF',
     params: [
@@ -337,9 +331,6 @@ const TEST_TYPES = {
         /toRdf-manifest#te075$/,
         /toRdf-manifest#te111$/,
         /toRdf-manifest#te112$/,
-        // direction (compound-literal)
-        /toRdf-manifest#tdi11$/,
-        /toRdf-manifest#tdi12$/,
       ]
     },
     fn: 'toRDF',