diff --git a/nomad/app/v1/models/graph/graph_models.py b/nomad/app/v1/models/graph/graph_models.py
index b3c833601ba9b1065c1cdf0df93a94110a658222..ff4b2151c21999bbcf866d2c235e589bbba52142 100644
--- a/nomad/app/v1/models/graph/graph_models.py
+++ b/nomad/app/v1/models/graph/graph_models.py
@@ -21,7 +21,12 @@ from typing import Optional, List, Union, Any, Literal
 from pydantic import BaseModel, Field, Extra
 
 from nomad.metainfo import Package
-from nomad.graph.model import RequestConfig, DatasetQuery
+from nomad.graph.model import (
+    RequestConfig,
+    DatasetQuery,
+    MetainfoQuery,
+    MetainfoPagination,
+)
 from nomad.metainfo.pydantic_extension import PydanticModel
 from nomad.datamodel.data import User as UserModel
 from nomad.app.v1.models.models import Metadata, MetadataResponse
@@ -217,7 +222,20 @@ class GraphDatasets(BaseModel):
     m_children: GraphDataset
 
 
+class MetainfoRequestOptions(BaseModel):
+    pagination: Optional[MetainfoPagination]
+    query: Optional[MetainfoQuery]
+
+
+class MetainfoResponseOptions(BaseModel):
+    pagination: Optional[PaginationResponse]
+    query: Optional[MetainfoQuery]
+
+
 class GraphMetainfo(BaseModel):
+    m_request: MetainfoRequestOptions
+    m_response: MetainfoResponseOptions
+    m_errors: List[Error]
     m_children: MSection
 
 
diff --git a/nomad/graph/graph_reader.py b/nomad/graph/graph_reader.py
index eae2bd8aed531a0fe4476e16ff8a1be50fc3e28b..29ded7015f26658106c778373e182217a1daa4b4 100644
--- a/nomad/graph/graph_reader.py
+++ b/nomad/graph/graph_reader.py
@@ -24,9 +24,12 @@ import functools
 import itertools
 import os
 import re
+from collections.abc import Iterator, AsyncIterator
+from threading import Lock
 from typing import Any, Callable, Type
 
 import orjson
+from cachetools import TTLCache
 from fastapi import HTTPException
 from mongoengine import Q
 
@@ -49,6 +52,9 @@ from nomad.app.v1.routers.uploads import (
     RawDirPagination,
 )
 from nomad.archive import ArchiveList, ArchiveDict, to_json
+from nomad.datamodel import ServerContext, User, EntryArchive, Dataset
+from nomad.datamodel.util import parse_path
+from nomad.files import UploadFiles, RawPathInfo
 from nomad.graph.model import (
     RequestConfig,
     DefinitionType,
@@ -56,10 +62,9 @@ from nomad.graph.model import (
     ResolveType,
     DatasetQuery,
     EntryQuery,
+    MetainfoQuery,
+    MetainfoPagination,
 )
-from nomad.datamodel import ServerContext, User, EntryArchive, Dataset
-from nomad.datamodel.util import parse_path
-from nomad.files import UploadFiles, RawPathInfo
 from nomad.metainfo import (
     SubSection,
     QuantityReference,
@@ -70,7 +75,6 @@ from nomad.metainfo import (
     Definition,
     Section,
 )
-
 from nomad.metainfo.data_type import Any as AnyType, JSON
 from nomad.metainfo.util import split_python_definition, MSubSectionList
 from nomad.processing import Entry, Upload, ProcessStatus
@@ -396,7 +400,14 @@ def _to_response_config(config: RequestConfig, exclude: list = None, **kwargs):
     return response_config
 
 
-async def _populate_result(container_root: dict, path: list, value, *, path_like=False):
+async def _populate_result(
+    container_root: dict,
+    path: list,
+    value,
+    *,
+    path_like=False,
+    overwrite_existing_str=False,
+):
     """
     For the given path and the root of the target container, populate the value.
 
@@ -476,6 +487,8 @@ async def _populate_result(container_root: dict, path: list, value, *, path_like
         assert isinstance(key_or_index, int)
         if target_container[key_or_index] is None:
             target_container[key_or_index] = new_value
+        elif isinstance(target_container[key_or_index], str) and overwrite_existing_str:
+            target_container[key_or_index] = new_value
         elif isinstance(new_value, dict):
             _merge_dict(target_container[key_or_index], new_value)
         elif isinstance(new_value, list):
@@ -484,7 +497,12 @@ async def _populate_result(container_root: dict, path: list, value, *, path_like
             target_container[key_or_index] = new_value
     elif isinstance(target_container, dict):
         assert isinstance(key_or_index, str)
-        if isinstance(new_value, dict):
+        if (
+            isinstance(target_container.get(key_or_index, None), str)
+            and overwrite_existing_str
+        ):
+            target_container[key_or_index] = new_value
+        elif isinstance(new_value, dict):
             target_container.setdefault(key_or_index, {})
             _merge_dict(target_container[key_or_index], new_value)
         elif isinstance(new_value, list):
@@ -571,6 +589,9 @@ def _normalise_required(
     elif name == Token.SEARCH:
         reader_type = ElasticSearchReader
         can_query = True
+    elif name == Token.METAINFO:
+        reader_type = MetainfoBrowser
+        can_query = True
     elif name == Token.RAW or name == Token.MAINFILE:
         reader_type = FileSystemReader
     elif name == Token.ARCHIVE:
@@ -705,6 +726,10 @@ def _normalise_index(index: tuple | None, length: int) -> range:
     return range(_bound(start), _bound(end) + 1)
 
 
+def _unwrap_subsection(target):
+    return target.sub_section.m_resolved() if isinstance(target, SubSection) else target
+
+
 class GeneralReader:
     # controls the name of configuration
     # it will be extracted from the query dict to generate the configuration object
@@ -1046,6 +1071,111 @@ class GeneralReader:
         return asyncio.run(self.read(*args, **kwargs))
 
 
+# module level TTL cache for caching packages
+__lock_pool = Lock()
+__package_pool = TTLCache(maxsize=128, ttl=300)
+
+
+def _fetch_package(key: str) -> Package:
+    with __lock_pool:
+        return __package_pool.get(key, None)
+
+
+def _cache_package(key: str, package: Package):
+    with __lock_pool:
+        __package_pool[key] = package
+
+
+class ArchiveLikeReader(GeneralReader):
+    """
+    An abstract class for `ArchiveReader` and `DefinitionReader`.
+    """
+
+    # noinspection PyUnusedLocal
+    async def _retrieve_definition(
+        self,
+        m_def: str | None,
+        m_def_id: str | None = None,
+        node: GraphNode | None = None,
+    ):
+        """
+        Retrieve a definition from an archive.
+        The definition is identified by `m_def` and/or `m_def_id`.
+        It could be a local definition that is defined in the `definitions` section.
+        In this case, we initialise a new `Package` object and resolve the path.
+        It could also be a reference to another entry in another upload.
+        In this case, we need to load the archive.
+
+        todo: more flexible definition retrieval, accounting for definition id, mismatches, etc.
+        """
+
+        async def __resolve_definition_in_archive(
+            _root,
+            _path_stack: list,
+            _upload_id: str = None,
+            _entry_id: str = None,
+        ):
+            cache_key: str = f'{_upload_id}:{_entry_id}'
+
+            custom_package: Package | None = _fetch_package(cache_key)
+            if custom_package is None:
+                custom_package = Package.m_from_dict(
+                    await async_to_json(await goto_child(_root, 'definitions')),
+                    m_context=ServerContext(
+                        get_upload_with_read_access(
+                            _upload_id, self.user, include_others=True
+                        )
+                    ),
+                )
+                # package loaded in this way does not have an attached archive
+                # we manually set the upload_id and entry_id so that
+                # correct references can be generated in the corresponding method
+                custom_package.entry_id = _entry_id
+                custom_package.upload_id = _upload_id
+                custom_package.init_metainfo()
+                if (
+                    upload := Upload.objects(upload_id=_upload_id).first()
+                ) is not None and upload.published:
+                    _cache_package(cache_key, custom_package)
+
+            return custom_package.m_resolve_path(_path_stack)
+
+        if m_def is not None:
+            if m_def.startswith(('#/', '/')):
+                # appears to be a local definition
+                return await __resolve_definition_in_archive(
+                    node.archive_root,
+                    [v for v in m_def.split('/') if v not in ('', '#', 'definitions')],
+                    await goto_child(node.archive_root, ['metadata', 'upload_id']),
+                    await goto_child(node.archive_root, ['metadata', 'entry_id']),
+                )
+            # todo: !!!need to unify different formats!!!
+            # check if m_def matches the pattern 'entry_id:example_id.example_section.example_quantity'
+            if m_def.startswith('entry_id:'):
+                tokens = m_def[9:].split('.')
+                entry_id = tokens.pop(0)
+                entry_record = Entry.objects(entry_id=entry_id).first()
+                upload_id = entry_record.upload_id
+                if (
+                    cached_package := _fetch_package(f'{upload_id}:{entry_id}')
+                ) is not None:  # early fetch to avoid loading archive from disk
+                    return cached_package.m_resolve_path(tokens)
+                archive = self.load_archive(upload_id, entry_id)
+                return await __resolve_definition_in_archive(
+                    archive, tokens, upload_id, entry_id
+                )
+
+        # further consider when only m_def_id is given, etc.
+
+        # this is not likely to be reached
+        # it does not work anyway
+        proxy = SectionReference().normalize(m_def)
+        proxy.m_proxy_context = ServerContext(
+            get_upload_with_read_access(node.upload_id, self.user, include_others=True)
+        )
+        return proxy.section_cls.m_def
+
+
 class MongoReader(GeneralReader):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -1341,7 +1471,9 @@ class MongoReader(GeneralReader):
             if key in (GeneralReader.__CONFIG__, GeneralReader.__WILDCARD__):
                 continue
 
-            async def offload_read(reader_cls, *args, read_list=False):
+            async def offload_read(
+                reader_cls: Type[GeneralReader], *args, read_list=False
+            ):
                 try:
                     with reader_cls(value, **offload_pack) as reader:
                         await _populate_result(
@@ -1386,6 +1518,11 @@ class MongoReader(GeneralReader):
                 await offload_read(EntryReader, node.archive['entry_id'])
                 continue
 
+            if key == Token.METAINFO and self.__class__ is MongoReader:
+                # hitting the bottom of the current scope
+                await offload_read(MetainfoBrowser)
+                continue
+
             if isinstance(node.archive, dict) and isinstance(value, dict):
                 # treat it as a normal key
                 # and handle in applying resolver if it is a leaf node
@@ -2146,7 +2283,7 @@ class FileSystemReader(GeneralReader):
         return {}
 
 
-class ArchiveReader(GeneralReader):
+class ArchiveReader(ArchiveLikeReader):
     """
     This class provides functionalities to read an archive with the required fields.
     A sample query will look like the following.
@@ -2181,7 +2318,6 @@ class ArchiveReader(GeneralReader):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        self.package_pool: dict = {}
 
     @staticmethod
     def __if_strip(node: GraphNode, config: RequestConfig, *, depth_check: bool = True):
@@ -2601,70 +2737,6 @@ class ArchiveReader(GeneralReader):
             resolved_node.replace(definition=target.m_resolved()), config
         )
 
-    # noinspection PyUnusedLocal
-    async def _retrieve_definition(
-        self, m_def: str | None, m_def_id: str | None, node: GraphNode
-    ):
-        # todo: more flexible definition retrieval, accounting for definition id, mismatches, etc.
-        context = ServerContext(
-            get_upload_with_read_access(node.upload_id, self.user, include_others=True)
-        )
-
-        def __resolve_definition_in_archive(
-            _root: dict,
-            _path_stack: list,
-            _upload_id: str = None,
-            _entry_id: str = None,
-        ):
-            cache_key = f'{_upload_id}:{_entry_id}'
-
-            if cache_key not in self.package_pool:
-                custom_def_package: Package = Package.m_from_dict(
-                    _root, m_context=context
-                )
-                # package loaded in this way does not have an attached archive
-                # we manually set the upload_id and entry_id so that
-                # correct references can be generated in the corresponding method
-                custom_def_package.entry_id = _entry_id
-                custom_def_package.upload_id = _upload_id
-                custom_def_package.init_metainfo()
-                self.package_pool[cache_key] = custom_def_package
-
-            return self.package_pool[cache_key].m_resolve_path(_path_stack)
-
-        if m_def is not None:
-            if m_def.startswith(('#/', '/')):
-                # appears to be a local definition
-                return __resolve_definition_in_archive(
-                    await async_to_json(
-                        await goto_child(node.archive_root, 'definitions')
-                    ),
-                    [v for v in m_def.split('/') if v not in ('', '#', 'definitions')],
-                    await goto_child(node.archive_root, ['metadata', 'upload_id']),
-                    await goto_child(node.archive_root, ['metadata', 'entry_id']),
-                )
-            # todo: !!!need to unify different formats!!!
-            # check if m_def matches the pattern 'entry_id:example_id.example_section.example_quantity'
-            regex = re.compile(r'entry_id:(.+)(?:\.(.+))+')
-            if match := regex.match(m_def):
-                entry_id = match.groups()[0]
-                upload_id = Entry.objects(entry_id=entry_id).first().upload_id
-                archive = self.load_archive(upload_id, entry_id)
-                return __resolve_definition_in_archive(
-                    await async_to_json(await goto_child(archive, 'definitions')),
-                    list(match.groups()[1:]),
-                    upload_id,
-                    entry_id,
-                )
-
-        # further consider when only m_def_id is given, etc.
-
-        # this is not likely to be reached
-        # it does not work anyway
-        proxy = SectionReference().normalize(m_def)
-        proxy.m_proxy_context = context
-        return proxy.section_cls.m_def
-
     @classmethod
     def validate_config(cls, key: str, config: RequestConfig):
         if config.pagination is not None:
@@ -2685,7 +2757,7 @@ class ArchiveReader(GeneralReader):
             return reader.sync_read(archive)
 
 
-class DefinitionReader(GeneralReader):
+class DefinitionReader(ArchiveLikeReader):
     async def read(self, archive: Definition) -> dict:
         response: dict = {Token.DEF: {}}
 
@@ -2814,17 +2886,10 @@ class DefinitionReader(GeneralReader):
                     # should never reach here
                     raise  # noqa: PLE0704
 
-            def __unwrap_subsection(__archive):
-                return (
-                    __archive.sub_section.m_resolved()
-                    if isinstance(__archive, SubSection)
-                    else __archive
-                )
-
             if isinstance(value, RequestConfig):
                 # this is a leaf, resolve it according to the config
                 async def __resolve(__path, __target):
-                    __archive = __unwrap_subsection(__target)
+                    __archive = _unwrap_subsection(__target)
                     if __archive is node.archive:
                         return
                     await self._resolve(
@@ -2846,7 +2911,7 @@ class DefinitionReader(GeneralReader):
             elif isinstance(value, dict):
                 # this is a nested query, keep walking down the tree
                 async def __walk(__path, __target):
-                    __archive = __unwrap_subsection(__target)
+                    __archive = _unwrap_subsection(__target)
                     if __archive is node.archive:
                         return
                     await self._walk(
@@ -2891,14 +2956,14 @@ class DefinitionReader(GeneralReader):
                 else ref_type.target_section_def
             )
 
+        def __convert(m_def):
+            return _convert_ref_to_path_string(m_def.strict_reference())
+
         def __override_path(q, s, v, p):
             """
             Normalise all definition identifiers with unique global reference.
             """
 
-            def __convert(m_def):
-                return _convert_ref_to_path_string(m_def.strict_reference())
-
             if isinstance(s, Quantity) and isinstance(v, dict):
                 if isinstance(s.type, Reference):
                     v['type_data'] = __convert(__unwrap_ref(s.type))
@@ -2917,6 +2982,36 @@ class DefinitionReader(GeneralReader):
 
             return v
 
+        def __unique_name(item):
+            # quantities may have identical names in different sections
+            # cannot just use the name as the key
+            # sections are guaranteed to have unique names
+            return (
+                f'{item.m_parent.name}.{item.name}'
+                if isinstance(item, Quantity)
+                else item.name
+            )
+
+        #
+        # the actual logic starts here
+        #
+
+        # the following forces definitions being resolved per package
+        if config.export_whole_package:
+            pkg = node.archive
+            while not isinstance(pkg, Package) and pkg.m_parent is not None:
+                pkg = pkg.m_parent
+            if pkg is not node.archive:
+                node = await self._switch_root(
+                    node.replace(archive=pkg),
+                    inplace=config.resolve_inplace,
+                )
+
+        # use current path as the unique package identifier
+        # instead of generating a new one from definition
+        if not config.if_include('/'.join(node.current_path)):
+            return
+
         # rewrite quantity type data with global reference
         if not self._check_cache(node.current_path, config.hash):
             self._cache_hash(node.current_path, config.hash)
@@ -2924,7 +3019,31 @@ class DefinitionReader(GeneralReader):
                 node.result_root,
                 node.current_path,
                 node.archive.m_to_dict(with_out_meta=True, transform=__override_path),
+                # the target location may contain a reference string already
+                # the string was added during switching root
+                # we allow it to be overwritten here
+                overwrite_existing_str=True,
             )
+            if isinstance(node.archive, Package):
+                # always export the following for packages
+                for name in ('all_quantities', 'all_sub_sections', 'all_base_sections'):
+                    container: set = set()
+                    for section in node.archive.section_definitions:
+                        target = getattr(section, name)
+                        container.update(
+                            _unwrap_subsection(v)
+                            for v in (
+                                target
+                                if isinstance(target, (list, set))
+                                else target.values()
+                            )
+                        )
+                    output: dict = {__unique_name(v): __convert(v) for v in container}
+                    await _populate_result(
+                        node.result_root,
+                        node.current_path + [name],
+                        {k: v for k, v in sorted(output.items(), key=lambda x: x[1])},
+                    )
 
         if isinstance(node.archive, Quantity):
             if isinstance(ref := node.archive.type, Reference):
@@ -2950,7 +3069,7 @@ class DefinitionReader(GeneralReader):
             return
 
         #
-        # the following is for section
+        # the following is for section and package
         #
 
         # no need to recursively resolve all relevant definitions if the directive is plain
@@ -2966,32 +3085,37 @@ class DefinitionReader(GeneralReader):
         ):
             return
 
-        for name, unwrap in (
-            ('extending_sections', False),
-            ('base_sections', False),
-            ('sub_sections', True),
-            ('quantities', False),
-        ):
-            for index, base in enumerate(getattr(node.archive, name, [])):
-                section = base.sub_section.m_resolved() if unwrap else base
-                ref_str = section.strict_reference()
-                path_stack = _convert_ref_to_path(ref_str)
-                if section is node.archive or self._check_cache(
-                    path_stack, config.hash
-                ):
-                    continue
-                await self._resolve(
-                    await self._switch_root(
-                        node.replace(
-                            archive=section,
-                            current_path=node.current_path + [name, str(index)],
-                            visited_path=node.visited_path.union({ref_str}),
-                            current_depth=node.current_depth + 1,
+        async def __visit(_definition, _items):
+            for _name in _items:
+                for _index, _base in enumerate(getattr(_definition, _name, [])):
+                    _section = _unwrap_subsection(_base)
+                    _ref_str = _section.strict_reference()
+                    _path_stack = _convert_ref_to_path(_ref_str)
+                    if _section is _definition or self._check_cache(
+                        _path_stack, config.hash
+                    ):
+                        continue
+                    await self._resolve(
+                        await self._switch_root(
+                            node.replace(
+                                archive=_section,
+                                current_path=node.current_path + [_name, str(_index)],
+                                visited_path=node.visited_path.union({_ref_str}),
+                                current_depth=node.current_depth + 1,
+                            ),
+                            inplace=config.resolve_inplace,
                         ),
-                        inplace=config.resolve_inplace,
-                    ),
-                    config,
-                )
+                        config,
+                    )
+
+        if isinstance(node.archive, Package):
+            for section in node.archive.section_definitions:
+                await __visit(section, ('extending_sections', 'base_sections'))
+        else:
+            await __visit(
+                node.archive,
+                ('extending_sections', 'base_sections', 'sub_sections', 'quantities'),
+            )
 
     @staticmethod
     async def _switch_root(node: GraphNode, *, inplace: bool) -> GraphNode:
@@ -3002,6 +3126,27 @@ class DefinitionReader(GeneralReader):
         if inplace:
             return node
 
+        if isinstance(node.archive, Package):
+            return node.replace(
+                result_root=node.ref_result_root,
+                current_path=[
+                    Token.UPLOADS,
+                    node.archive.upload_id,
+                    Token.ENTRIES,
+                    node.archive.entry_id,
+                    Token.ARCHIVE,
+                    'definitions',
+                ]  # reconstruct the path to the definition in the archive
+                if node.archive.entry_id and node.archive.upload_id
+                else [
+                    Token.METAINFO,
+                    node.archive.name,
+                ],  # otherwise a built-in package
+            )
+
+        # we always put a reference string at the current location
+        # since the section may belong to another package
+        # its definition may be placed in at the current location, or another location
         ref_str: str = node.archive.strict_reference()
         if not isinstance(node.archive, Quantity):
             await _populate_result(
@@ -3019,6 +3164,158 @@ class DefinitionReader(GeneralReader):
         return config
 
 
+class MetainfoBrowser(DefinitionReader):
+    """
+    A special implementation of definition reader.
+    """
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._pagination_response: dict | None = None
+
+    def _apply_query(self, config: RequestConfig) -> list[str]:
+        if config.query is None:
+            all_keys: list = list(Package.registry.keys())
+        else:
+            raise NotImplementedError
+            # todo: implement query based filtering
+
+        total: int = len(all_keys)
+
+        default_pagination = config.pagination
+        if default_pagination is not None:
+            assert isinstance(default_pagination, MetainfoPagination)
+            all_keys = default_pagination.order_result(all_keys)
+            all_keys = default_pagination.paginate_result(all_keys, None)
+        else:
+            default_pagination = MetainfoPagination()
+
+        # we use the class member to cache the response
+        # it will be written to the result tree later
+        # we do not direct perform writing here to avoid turning all methods async
+        self._pagination_response = default_pagination.dict()
+        self._pagination_response['total'] = total
+
+        return all_keys
+
+    def _filter_registry(self, config: RequestConfig, omit_keys=None) -> Iterator[str]:
+        """
+        Filter the registry based on the given config.
+        """
+        for pkg_name in self._apply_query(config):
+            if not config.if_include(pkg_name):
+                continue
+            if omit_keys is not None and pkg_name in omit_keys:
+                continue
+            yield pkg_name
+
+    async def _generate_package(
+        self,
+    ) -> AsyncIterator[tuple[str, Package, RequestConfig | dict]]:
+        if isinstance(self.required_query, RequestConfig):
+            for name in self._filter_registry(self.required_query):
+                yield name, Package.registry[name], self.required_query
+        else:
+            has_wildcard: bool = GeneralReader.__WILDCARD__ in self.required_query
+
+            if GeneralReader.__CONFIG__ in self.required_query:
+                current_config: RequestConfig = self.required_query[
+                    GeneralReader.__CONFIG__
+                ]
+                if has_wildcard:
+                    child_config = self.required_query[GeneralReader.__WILDCARD__]
+                else:
+                    child_config = current_config.new(
+                        {
+                            'index': None,  # ignore index requirements for children
+                            'query': None,  # ignore query for children
+                            'pagination': None,  # ignore pagination for children
+                        },
+                        retain_pattern=True,  # alert: should the include/exclude pattern be retained?
+                    )
+                for name in self._filter_registry(
+                    current_config, omit_keys=self.required_query.keys()
+                ):
+                    yield name, Package.registry[name], child_config
+            elif has_wildcard:
+                raise ValueError(
+                    'Wildcard is not supported when no parent config is defined.'
+                )
+
+            for key, value in self.required_query.items():
+                if key in (GeneralReader.__CONFIG__, GeneralReader.__WILDCARD__):
+                    continue
+
+                if key in Package.registry:
+                    yield key, Package.registry[key], value
+                elif key.startswith('entry_id:'):
+                    try:
+                        yield key, await self._retrieve_definition(key), value
+                    except Exception as e:
+                        self._log(f'Failed to retrieve definition: {e}')
+
+    async def read(self) -> dict:  # type: ignore # noqa
+        response: dict = {}
+
+        if self.global_root is None:
+            self.global_root = response
+            has_global_root: bool = False
+        else:
+            has_global_root = True
+
+        current_config = self.global_config
+        if isinstance(self.required_query, dict):
+            current_config = self.required_query.get(
+                GeneralReader.__CONFIG__, current_config
+            )
+
+        async for pkg_name, pkg_definition, pkg_query in self._generate_package():
+            response.setdefault(pkg_name, {})
+            await self._walk(
+                GraphNode(
+                    upload_id='__NONE__',
+                    entry_id='__NONE__',
+                    current_path=[pkg_name],
+                    result_root=response,
+                    ref_result_root=self.global_root,
+                    archive=pkg_definition,
+                    archive_root=None,
+                    definition=None,
+                    visited_path=set(),
+                    current_depth=0,
+                    reader=self,
+                ),
+                pkg_query,
+                current_config,
+            )
+
+        self._populate_error_list(response)
+
+        if not has_global_root:
+            self.global_root = None
+
+        if self._pagination_response is not None:
+            await _populate_result(
+                response, [Token.RESPONSE, 'pagination'], self._pagination_response
+            )
+            # reset the cache to ensure re-entrance
+            self._pagination_response = None
+
+        return response
+
+    @classmethod
+    def validate_config(cls, key: str, config: RequestConfig):
+        try:
+            if config.query is not None:
+                config.query = MetainfoQuery.parse_obj(config.query)
+            if config.pagination is not None:
+                config.pagination = MetainfoPagination.parse_obj(config.pagination)
+        except Exception as e:
+            raise ConfigError(str(e))
+
+        return config
+
+
 __M_SEARCHABLE__: dict = {
     Token.SEARCH: ElasticSearchReader,
     Token.METADATA: ElasticSearchReader,
diff --git a/nomad/graph/model.py b/nomad/graph/model.py
index d9ff44bf51b4a5605a0f38c47e8fd894b3455bc2..c726b45536bc65b6fcb0a3bc1da6ab4353d830e7 100644
--- a/nomad/graph/model.py
+++ b/nomad/graph/model.py
@@ -25,7 +25,7 @@ from typing import FrozenSet, Optional, Union
 
 from pydantic import BaseModel, Field, Extra, ValidationError, validator
 
-from nomad.app.v1.models import MetadataPagination, Metadata
+from nomad.app.v1.models import MetadataPagination, Metadata, Pagination, Direction
 from nomad.app.v1.routers.datasets import DatasetPagination
 from nomad.app.v1.routers.uploads import (
     UploadProcDataQuery,
@@ -65,6 +65,37 @@ class EntryQuery(BaseModel):
     )
 
 
+class MetainfoQuery(BaseModel):
+    pass
+
+
+class MetainfoPagination(Pagination):
+    def order_result(self, result):
+        return list(sorted(result, reverse=self.order == Direction.desc))
+
+    def paginate_result(self, result, pick_value):
+        if self.page is not None:
+            start = (self.page - 1) * self.page_size
+            end = start + self.page_size
+        elif self.page_offset is not None:
+            start = self.page_offset
+            end = start + self.page_size
+        elif self.page_after_value is not None:
+            start = 0
+            for index, item in enumerate(result):
+                if item == self.page_after_value:
+                    start = index + 1
+                    break
+            end = start + self.page_size
+        else:
+            start, end = 0, self.page_size
+
+        total_size = len(result)
+        first, last = min(start, total_size), min(end, total_size)
+
+        return [] if first == last else result[first:last]
+
+
 class DirectiveType(Enum):
     plain = 'plain'
     resolved = 'resolved'
@@ -187,6 +218,13 @@ class RequestConfig(BaseModel):
         The resolved quantity/section will be placed in the same archive.
         """,
     )
+    export_whole_package: bool = Field(
+        False,
+        description="""
+        Set to `True` to always get the whole package.
+        Set to `False` to definitions per section basis.
+        """,
+    )
     include_definition: DefinitionType = Field(
         DefinitionType.none,
         description="""
@@ -220,6 +258,7 @@ class RequestConfig(BaseModel):
         UploadProcDataPagination,
         MetadataPagination,
         EntryProcDataPagination,
+        MetainfoPagination,
     ] = Field(
         None,
         description="""
@@ -229,7 +268,9 @@ class RequestConfig(BaseModel):
         Please refer to `DatasetPagination`, `UploadProcDataPagination`, `MetadataPagination` for details.
         """,
     )
-    query: Union[dict, DatasetQuery, UploadProcDataQuery, Metadata, EntryQuery] = Field(
+    query: Union[
+        dict, DatasetQuery, UploadProcDataQuery, Metadata, EntryQuery, MetainfoQuery
+    ] = Field(
         None,
         description="""
         The query configuration used for either mongo or elastic search.
diff --git a/nomad/metainfo/metainfo.py b/nomad/metainfo/metainfo.py
index 3e8305e6df34c4173b45b9e54d00d2b5349b884e..3574c029217cabc94e8fe9c44bc47be990c2df77 100644
--- a/nomad/metainfo/metainfo.py
+++ b/nomad/metainfo/metainfo.py
@@ -4075,6 +4075,9 @@ class Package(Definition):
         return super().qualified_name()
 
     def m_resolve_path(self, path_stack: list):
+        if len(path_stack) == 0:
+            return self
+
         path_str = '/'.join(path_stack)
         current_pos = path_stack.pop(0)
         section = self.all_definitions.get(current_pos, None)
diff --git a/tests/graph/test_definition_reader.py b/tests/graph/test_definition_reader.py
index c7ece5f3091de5d23e31a970f4932399155ceabc..f67385897f1c1994fd2602742007eb02aff75e4d 100644
--- a/tests/graph/test_definition_reader.py
+++ b/tests/graph/test_definition_reader.py
@@ -139,6 +139,16 @@ def assert_dict(d1, d2):
             },
             id='plain-retrieval',
         ),
+        pytest.param(
+            {
+                'm_request': {
+                    'directive': 'plain',
+                    'exclude': ['*test_definition_reader*'],
+                }
+            },
+            {'m_def': f'{prefix}/3'},
+            id='plain-retrieval-exclude',
+        ),
         # now resolve all referenced quantities and sections
         pytest.param(
             {'m_request': {'directive': 'resolved'}},
@@ -584,7 +594,7 @@ def assert_dict(d1, d2):
         ),
     ],
 )
-def test_definition_reader(query, result):
+def test_definition_reader(query: dict, result: dict):
     with DefinitionReader(query) as reader:
         response = remove_cache(reader.sync_read(m_def))
-    assert_dict(response, result)
+        assert_dict(response, result)
diff --git a/tests/graph/test_graph_reader.py b/tests/graph/test_graph_reader.py
index 2f254db37a27e09d7b2c25ceb356229dcc5514ee..091c65c344b637cce6051abadb0bb823dd4a4c2c 100644
--- a/tests/graph/test_graph_reader.py
+++ b/tests/graph/test_graph_reader.py
@@ -2511,6 +2511,505 @@ def test_general_reader(json_dict, example_data_with_reference, user1):
     )
 
 
+# noinspection DuplicatedCode,SpellCheckingInspection
+def test_metainfo_reader(mongo_infra, user1):
+    def increment():
+        n = 0
+        while True:
+            n += 1
+            yield n
+
+    counter = increment()
+
+    def __ge_print(msg, required, *, to_file: bool = False, result: dict = None):
+        with MongoReader(required, user=user1) as reader:
+            if result:
+                assert_dict(reader.sync_read(), result)
+            else:
+                rprint(f'\n\nExample: {next(counter)} -> {msg}:')
+                rprint(required)
+                if not to_file:
+                    rprint('output:')
+                    rprint(reader.sync_read())
+                else:
+                    with open('archive_reader_test.json', 'w') as f:
+                        f.write(json.dumps(reader.sync_read()))
+
+    __ge_print(
+        'general start from metainfo',
+        {
+            Token.METAINFO: {
+                'nomad.datamodel.metainfo.simulation.run': {
+                    'section_definitions[2]': {
+                        'm_request': {'directive': 'plain'},
+                    }
+                }
+            }
+        },
+        result={
+            'metainfo': {
+                'nomad.datamodel.metainfo.simulation.run': {
+                    'section_definitions': [
+                        None,
+                        None,
+                        {
+                            'name': 'MessageRun',
+                            'description': 'Contains warning, error, and info messages of the run.',
+                            'quantities': [
+                                {
+                                    'name': 'type',
+                                    'description': 'Type of the message. Can be one of warning, error, info, debug.',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'shape': [],
+                                },
+                                {
+                                    'name': 'value',
+                                    'description': 'Value of the message of the computational program, given by type.',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'shape': [],
+                                },
+                            ],
+                        },
+                    ]
+                }
+            },
+        },
+    )
+
+    __ge_print(
+        'general start from metainfo',
+        {
+            Token.METAINFO: {
+                'm_request': {
+                    'include': ['*nomad.datamodel.metainfo.simulation.run'],
+                    'pagination': {'page_size': 50},
+                },
+                '*': {'m_request': {'index': [2]}},
+            }
+        },
+        result={
+            'metainfo': {
+                'nomad.datamodel.metainfo.simulation.run': {
+                    'name': 'nomad.datamodel.metainfo.simulation.run',
+                    'section_definitions': [
+                        {
+                            'name': 'Program',
+                            'description': 'Contains the specifications of the program.',
+                            'quantities': [
+                                {
+                                    'name': 'name',
+                                    'description': 'Specifies the name of the program that generated the data.',
+                                    'categories': [
+                                        '/packages/0/category_definitions/0',
+                                        '/packages/0/category_definitions/1',
+                                    ],
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'shape': [],
+                                },
+                                {
+                                    'name': 'version',
+                                    'description': 'Specifies the official release version of the program that was used.',
+                                    'categories': [
+                                        '/packages/0/category_definitions/0',
+                                        '/packages/0/category_definitions/1',
+                                    ],
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'shape': [],
+                                },
+                                {
+                                    'name': 'version_internal',
+                                    'description': 'Specifies a program version tag used internally for development purposes.\nAny kind of tagging system is supported, including git commit hashes.',
+                                    'categories': [
+                                        '/packages/0/category_definitions/1'
+                                    ],
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                                {
+                                    'name': 'compilation_datetime',
+                                    'description': 'Contains the program compilation date and time from *Unix epoch* (00:00:00 UTC on\n1 January 1970) in seconds. For date and times without a timezone, the default\ntimezone GMT is used.',
+                                    'categories': [
+                                        '/packages/0/category_definitions/0',
+                                        '/packages/0/category_definitions/1',
+                                    ],
+                                    'type': {
+                                        'type_kind': 'numpy',
+                                        'type_data': 'float64',
+                                    },
+                                    'shape': [],
+                                    'unit': 'second',
+                                },
+                                {
+                                    'name': 'compilation_host',
+                                    'description': 'Specifies the host on which the program was compiled.',
+                                    'categories': [
+                                        '/packages/0/category_definitions/0',
+                                        '/packages/0/category_definitions/1',
+                                    ],
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'shape': [],
+                                },
+                            ],
+                        },
+                        {
+                            'name': 'TimeRun',
+                            'description': 'Contains information on timing information of the run.',
+                            'quantities': [
+                                {
+                                    'name': 'date_end',
+                                    'description': 'Stores the end date of the run as time since the *Unix epoch* (00:00:00 UTC on 1\nJanuary 1970) in seconds. For date and times without a timezone, the default\ntimezone GMT is used.',
+                                    'type': {
+                                        'type_kind': 'numpy',
+                                        'type_data': 'float64',
+                                    },
+                                    'shape': [],
+                                    'unit': 'second',
+                                },
+                                {
+                                    'name': 'date_start',
+                                    'description': 'Stores the start date of the run as time since the *Unix epoch* (00:00:00 UTC on 1\nJanuary 1970) in seconds. For date and times without a timezone, the default\ntimezone GMT is used.',
+                                    'type': {
+                                        'type_kind': 'numpy',
+                                        'type_data': 'float64',
+                                    },
+                                    'shape': [],
+                                    'unit': 'second',
+                                },
+                                {
+                                    'name': 'cpu1_end',
+                                    'description': 'Stores the end time of the run on CPU 1.',
+                                    'type': {
+                                        'type_kind': 'numpy',
+                                        'type_data': 'float64',
+                                    },
+                                    'shape': [],
+                                    'unit': 'second',
+                                },
+                                {
+                                    'name': 'cpu1_start',
+                                    'description': 'Stores the start time of the run on CPU 1.',
+                                    'type': {
+                                        'type_kind': 'numpy',
+                                        'type_data': 'float64',
+                                    },
+                                    'shape': [],
+                                    'unit': 'second',
+                                },
+                                {
+                                    'name': 'wall_end',
+                                    'description': 'Stores the internal wall-clock time at the end of the run.',
+                                    'type': {
+                                        'type_kind': 'numpy',
+                                        'type_data': 'float64',
+                                    },
+                                    'shape': [],
+                                    'unit': 'second',
+                                },
+                                {
+                                    'name': 'wall_start',
+                                    'description': 'Stores the internal wall-clock time from the start of the run.',
+                                    'type': {
+                                        'type_kind': 'numpy',
+                                        'type_data': 'float64',
+                                    },
+                                    'shape': [],
+                                    'unit': 'second',
+                                },
+                            ],
+                        },
+                        {
+                            'name': 'MessageRun',
+                            'description': 'Contains warning, error, and info messages of the run.',
+                            'quantities': [
+                                {
+                                    'name': 'type',
+                                    'description': 'Type of the message. Can be one of warning, error, info, debug.',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'shape': [],
+                                },
+                                {
+                                    'name': 'value',
+                                    'description': 'Value of the message of the computational program, given by type.',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'shape': [],
+                                },
+                            ],
+                        },
+                        {
+                            'name': 'Run',
+                            'description': 'Every section run represents a single call of a program.',
+                            'base_sections': [
+                                'metainfo/nomad.datamodel.data/section_definitions/0'
+                            ],
+                            'quantities': [
+                                {
+                                    'name': 'calculation_file_uri',
+                                    'description': 'Contains the nomad uri of a raw the data file connected to the current run. There\nshould be an value for the main_file_uri and all ancillary files.',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'shape': [],
+                                },
+                                {
+                                    'name': 'clean_end',
+                                    'description': 'Indicates whether this run terminated properly (true), or if it was killed or\nexited with an error code unequal to zero (false).',
+                                    'type': {
+                                        'type_kind': 'python',
+                                        'type_data': 'bool',
+                                    },
+                                    'shape': [],
+                                },
+                                {
+                                    'name': 'raw_id',
+                                    'description': 'An optional calculation id, if one is found in the code input/output files.',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'shape': [],
+                                },
+                                {
+                                    'name': 'starting_run_ref',
+                                    'description': 'Links the current section run to a section run containing the calculations from\nwhich the current section starts.',
+                                    'categories': ['/category_definitions/0'],
+                                    'type': {
+                                        'type_kind': 'reference',
+                                        'type_data': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/3',
+                                    },
+                                    'shape': [],
+                                },
+                                {
+                                    'name': 'n_references',
+                                    'description': 'Number of references to the current section calculation.',
+                                    'type': {
+                                        'type_kind': 'numpy',
+                                        'type_data': 'int32',
+                                    },
+                                    'shape': [],
+                                },
+                                {
+                                    'name': 'runs_ref',
+                                    'description': 'Links the the current section to other run sections. Such a link is necessary for\nexample for workflows that may contain a series of runs.',
+                                    'categories': ['/category_definitions/0'],
+                                    'type': {
+                                        'type_kind': 'reference',
+                                        'type_data': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/3',
+                                    },
+                                    'shape': ['n_references'],
+                                },
+                            ],
+                            'sub_sections': [
+                                {
+                                    'name': 'program',
+                                    'sub_section': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/0',
+                                },
+                                {
+                                    'name': 'time_run',
+                                    'sub_section': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/1',
+                                },
+                                {
+                                    'name': 'message',
+                                    'sub_section': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/2',
+                                },
+                                {
+                                    'name': 'method',
+                                    'sub_section': 'metainfo/nomad.datamodel.metainfo.simulation.method/section_definitions/44',
+                                    'repeats': True,
+                                },
+                                {
+                                    'name': 'system',
+                                    'sub_section': 'metainfo/nomad.datamodel.metainfo.simulation.system/section_definitions/8',
+                                    'repeats': True,
+                                },
+                                {
+                                    'name': 'calculation',
+                                    'sub_section': 'metainfo/nomad.datamodel.metainfo.simulation.calculation/section_definitions/36',
+                                    'repeats': True,
+                                },
+                            ],
+                        },
+                    ],
+                    'category_definitions': [
+                        {
+                            'name': 'AccessoryInfo',
+                            'description': 'Information that *in theory* should not affect the results of the calculations (e.g.,\ntiming).',
+                        },
+                        {
+                            'name': 'ProgramInfo',
+                            'description': 'Contains information on the program that generated the data, i.e. the program_name,\nprogram_version, program_compilation_host and program_compilation_datetime as direct\nchildren of this field.',
+                            'categories': ['/packages/0/category_definitions/0'],
+                        },
+                    ],
+                    'all_base_sections': {
+                        'ArchiveSection': 'metainfo/nomad.datamodel.data/section_definitions/0'
+                    },
+                    'all_quantities': {
+                        'MessageRun.value': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/2/quantities/1',
+                        'Run.calculation_file_uri': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/3/quantities/0',
+                        'TimeRun.date_start': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/1/quantities/1',
+                        'Run.n_references': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/3/quantities/4',
+                        'Program.name': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/0/quantities/0',
+                        'TimeRun.date_end': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/1/quantities/0',
+                        'Run.starting_run_ref': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/3/quantities/3',
+                        'Run.raw_id': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/3/quantities/2',
+                        'Program.compilation_host': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/0/quantities/4',
+                        'TimeRun.wall_end': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/1/quantities/4',
+                        'TimeRun.cpu1_start': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/1/quantities/3',
+                        'TimeRun.cpu1_end': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/1/quantities/2',
+                        'Run.clean_end': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/3/quantities/1',
+                        'TimeRun.wall_start': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/1/quantities/5',
+                        'MessageRun.type': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/2/quantities/0',
+                        'Program.version': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/0/quantities/1',
+                        'Program.compilation_datetime': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/0/quantities/3',
+                        'Program.version_internal': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/0/quantities/2',
+                        'Run.runs_ref': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/3/quantities/5',
+                    },
+                    'all_sub_sections': {
+                        'MessageRun': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/2',
+                        'TimeRun': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/1',
+                        'System': 'metainfo/nomad.datamodel.metainfo.simulation.system/section_definitions/8',
+                        'Method': 'metainfo/nomad.datamodel.metainfo.simulation.method/section_definitions/44',
+                        'Calculation': 'metainfo/nomad.datamodel.metainfo.simulation.calculation/section_definitions/36',
+                        'Program': 'metainfo/nomad.datamodel.metainfo.simulation.run/section_definitions/0',
+                    },
+                },
+            },
+        },
+    )
+
+    __ge_print(
+        'general start from metainfo',
+        {
+            Token.METAINFO: {
+                'm_request': {
+                    'include': ['*test_data'],
+                    'pagination': {'page_size': 50},
+                }
+            }
+        },
+        result={
+            'metainfo': {
+                'tests.processing.test_data': {
+                    'name': 'tests.processing.test_data',
+                    'section_definitions': [
+                        {
+                            'name': 'TestBatchSample',
+                            'base_sections': [
+                                'metainfo/nomad.datamodel.data/section_definitions/1'
+                            ],
+                            'quantities': [
+                                {
+                                    'name': 'batch_id',
+                                    'description': 'Id for the batch',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                                {
+                                    'name': 'sample_number',
+                                    'description': 'Sample index',
+                                    'type': {'type_kind': 'python', 'type_data': 'int'},
+                                },
+                                {
+                                    'm_annotations': {
+                                        'eln': [{'component': 'RichTextEditQuantity'}]
+                                    },
+                                    'name': 'comments',
+                                    'description': 'Comments',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                            ],
+                        },
+                        {
+                            'name': 'TestBatch',
+                            'base_sections': [
+                                'metainfo/nomad.datamodel.data/section_definitions/1'
+                            ],
+                            'quantities': [
+                                {
+                                    'm_annotations': {
+                                        'eln': [{'component': 'StringEditQuantity'}]
+                                    },
+                                    'name': 'batch_id',
+                                    'description': 'Id for the batch',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                                {
+                                    'm_annotations': {
+                                        'eln': [{'component': 'NumberEditQuantity'}]
+                                    },
+                                    'name': 'n_samples',
+                                    'description': 'Number of samples in batch',
+                                    'type': {'type_kind': 'python', 'type_data': 'int'},
+                                },
+                                {
+                                    'name': 'sample_refs',
+                                    'more': {
+                                        'descriptions': 'The samples in the batch.',
+                                        'type_data': 'metainfo/tests.processing.test_data/section_definitions/0',
+                                    },
+                                    'type': {
+                                        'type_kind': 'reference',
+                                        'type_data': 'metainfo/tests.processing.test_data/section_definitions/0',
+                                    },
+                                    'shape': ['*'],
+                                },
+                            ],
+                        },
+                        {
+                            'name': 'TestSection',
+                            'base_sections': [
+                                'metainfo/nomad.datamodel.data/section_definitions/0'
+                            ],
+                        },
+                        {
+                            'name': 'TestReferenceSection',
+                            'base_sections': [
+                                'metainfo/nomad.datamodel.data/section_definitions/0'
+                            ],
+                            'quantities': [
+                                {
+                                    'name': 'reference',
+                                    'type': {
+                                        'type_kind': 'reference',
+                                        'type_data': 'metainfo/tests.processing.test_data/section_definitions/2',
+                                    },
+                                }
+                            ],
+                        },
+                        {
+                            'name': 'TestData',
+                            'base_sections': [
+                                'metainfo/nomad.datamodel.data/section_definitions/1'
+                            ],
+                            'sub_sections': [
+                                {
+                                    'name': 'test_section',
+                                    'sub_section': 'metainfo/tests.processing.test_data/section_definitions/2',
+                                    'repeats': True,
+                                },
+                                {
+                                    'name': 'reference_section',
+                                    'sub_section': 'metainfo/tests.processing.test_data/section_definitions/3',
+                                },
+                            ],
+                        },
+                    ],
+                    'all_base_sections': {
+                        'ArchiveSection': 'metainfo/nomad.datamodel.data/section_definitions/0',
+                        'EntryData': 'metainfo/nomad.datamodel.data/section_definitions/1',
+                    },
+                    'all_quantities': {
+                        'TestBatch.batch_id': 'metainfo/tests.processing.test_data/section_definitions/1/quantities/0',
+                        'TestReferenceSection.reference': 'metainfo/tests.processing.test_data/section_definitions/3/quantities/0',
+                        'TestBatchSample.batch_id': 'metainfo/tests.processing.test_data/section_definitions/0/quantities/0',
+                        'TestBatch.sample_refs': 'metainfo/tests.processing.test_data/section_definitions/1/quantities/2',
+                        'TestBatchSample.sample_number': 'metainfo/tests.processing.test_data/section_definitions/0/quantities/1',
+                        'TestBatch.n_samples': 'metainfo/tests.processing.test_data/section_definitions/1/quantities/1',
+                        'TestBatchSample.comments': 'metainfo/tests.processing.test_data/section_definitions/0/quantities/2',
+                    },
+                    'all_sub_sections': {
+                        'TestSection': 'metainfo/tests.processing.test_data/section_definitions/2',
+                        'TestReferenceSection': 'metainfo/tests.processing.test_data/section_definitions/3',
+                    },
+                },
+            },
+        },
+    )
+
+
 # noinspection DuplicatedCode,SpellCheckingInspection
 def test_general_reader_search(json_dict, example_data_with_reference, user1):
     def increment():
@@ -2700,6 +3199,7 @@ def test_custom_schema_archive_and_definition(user1, custom_data):
                     'm_def': {
                         'm_request': {
                             'directive': 'plain',
+                            'export_whole_package': True,
                         },
                     },
                 }
@@ -2719,6 +3219,327 @@ def test_custom_schema_archive_and_definition(user1, custom_data):
             'process_status': 'SUCCESS',
             'upload_id': 'id_custom',
             'warnings': [],
+            'uploads': {
+                'id_custom': {
+                    'entries': {
+                        'id_example': {
+                            'archive': {
+                                'definitions': {
+                                    'section_definitions': [
+                                        {
+                                            'name': 'MySection',
+                                            'base_sections': [
+                                                'metainfo/nomad.datamodel.data/section_definitions/1'
+                                            ],
+                                            'quantities': [
+                                                {
+                                                    'name': 'my_quantity',
+                                                    'type': {
+                                                        'type_kind': 'python',
+                                                        'type_data': 'str',
+                                                    },
+                                                },
+                                                {
+                                                    'name': 'datetime_list',
+                                                    'type': {
+                                                        'type_kind': 'custom',
+                                                        'type_data': 'nomad.metainfo.data_type.Datetime',
+                                                    },
+                                                    'shape': ['*'],
+                                                },
+                                            ],
+                                        }
+                                    ],
+                                    'name': 'test_package_name',
+                                    'all_base_sections': {
+                                        'ArchiveSection': 'metainfo/nomad.datamodel.data/section_definitions/0',
+                                        'EntryData': 'metainfo/nomad.datamodel.data/section_definitions/1',
+                                    },
+                                    'all_quantities': {
+                                        'MySection.my_quantity': 'uploads/id_custom/entries/id_example/archive/definitions/section_definitions/0/quantities/0',
+                                        'MySection.datetime_list': 'uploads/id_custom/entries/id_example/archive/definitions/section_definitions/0/quantities/1',
+                                    },
+                                    'all_sub_sections': {},
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            'archive': {
+                'data': {
+                    'datetime_list': [
+                        '2022-04-01T00:00:00+00:00',
+                        '2022-04-02T00:00:00+00:00',
+                    ],
+                    'my_quantity': 'test_value',
+                    'm_def': {
+                        'm_def': 'uploads/id_custom/entries/id_example/archive/definitions/section_definitions/0'
+                    },
+                }
+            },
+        },
+    )
+
+    __entry_print(
+        'custom',
+        {
+            Token.ARCHIVE: {
+                'data': {
+                    'm_def': {
+                        'm_request': {
+                            'directive': 'resolved',
+                            'export_whole_package': True,
+                            'depth': 1,
+                        },
+                    },
+                }
+            },
+        },
+        result={
+            'uploads': {
+                'id_custom': {
+                    'entries': {
+                        'id_example': {
+                            'archive': {
+                                'definitions': {
+                                    'name': 'test_package_name',
+                                    'section_definitions': [
+                                        {
+                                            'name': 'MySection',
+                                            'base_sections': [
+                                                'metainfo/nomad.datamodel.data/section_definitions/1'
+                                            ],
+                                            'quantities': [
+                                                {
+                                                    'name': 'my_quantity',
+                                                    'type': {
+                                                        'type_kind': 'python',
+                                                        'type_data': 'str',
+                                                    },
+                                                },
+                                                {
+                                                    'name': 'datetime_list',
+                                                    'type': {
+                                                        'type_kind': 'custom',
+                                                        'type_data': 'nomad.metainfo.data_type.Datetime',
+                                                    },
+                                                    'shape': ['*'],
+                                                },
+                                            ],
+                                        }
+                                    ],
+                                    'all_quantities': {
+                                        'MySection.my_quantity': 'uploads/id_custom/entries/id_example/archive/definitions/section_definitions/0/quantities/0',
+                                        'MySection.datetime_list': 'uploads/id_custom/entries/id_example/archive/definitions/section_definitions/0/quantities/1',
+                                    },
+                                    'all_sub_sections': {},
+                                    'all_base_sections': {
+                                        'ArchiveSection': 'metainfo/nomad.datamodel.data/section_definitions/0',
+                                        'EntryData': 'metainfo/nomad.datamodel.data/section_definitions/1',
+                                    },
+                                    'base_sections': [
+                                        'metainfo/nomad.datamodel.data/section_definitions/1'
+                                    ],
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            'metainfo': {
+                'nomad.datamodel.data': {
+                    'name': 'nomad.datamodel.data',
+                    'section_definitions': [
+                        {
+                            'name': 'ArchiveSection',
+                            'description': 'Base class for sections in a NOMAD archive. Provides a framework for custom section normalization via the `normalize` function.',
+                        },
+                        {
+                            'name': 'EntryData',
+                            'description': 'An empty base section definition. This can be used to add new top-level sections to an entry.',
+                            'base_sections': [
+                                'metainfo/nomad.datamodel.data/section_definitions/0'
+                            ],
+                        },
+                        {
+                            'name': 'Author',
+                            'description': 'A person that is author of data in NOMAD or references by NOMAD.',
+                            'quantities': [
+                                {
+                                    'm_annotations': {
+                                        'elasticsearch': [
+                                            'viewers.name',
+                                            'viewers.name.text',
+                                            'viewers.name__suggestion',
+                                        ]
+                                    },
+                                    'name': 'name',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                    'virtual': True,
+                                },
+                                {
+                                    'name': 'first_name',
+                                    'description': 'The users first name (including all other given names)',
+                                    'type': {
+                                        'type_kind': 'custom',
+                                        'type_data': 'nomad.metainfo.data_type.Capitalized',
+                                    },
+                                },
+                                {
+                                    'name': 'last_name',
+                                    'description': 'The users last name',
+                                    'type': {
+                                        'type_kind': 'custom',
+                                        'type_data': 'nomad.metainfo.data_type.Capitalized',
+                                    },
+                                },
+                                {
+                                    'name': 'email',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                                {
+                                    'name': 'affiliation',
+                                    'description': 'The name of the company and institutes the user identifies with',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                                {
+                                    'name': 'affiliation_address',
+                                    'description': 'The address of the given affiliation',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                            ],
+                        },
+                        {
+                            'm_annotations': {'pydantic': ['PydanticModel']},
+                            'name': 'User',
+                            'description': 'A NOMAD user. Typically a NOMAD user has a NOMAD account. The user related data is managed by\nNOMAD keycloak user-management system. Users are used to denote authors,\nreviewers, and owners of datasets.',
+                            'base_sections': [
+                                'metainfo/nomad.datamodel.data/section_definitions/2'
+                            ],
+                            'quantities': [
+                                {
+                                    'm_annotations': {
+                                        'elasticsearch': ['viewers.user_id']
+                                    },
+                                    'name': 'user_id',
+                                    'description': 'The unique, persistent keycloak UUID',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                                {
+                                    'name': 'username',
+                                    'description': 'The unique, persistent, user chosen username',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                                {
+                                    'name': 'created',
+                                    'description': 'The time the account was created',
+                                    'type': {
+                                        'type_kind': 'custom',
+                                        'type_data': 'nomad.metainfo.data_type.Datetime',
+                                    },
+                                },
+                                {
+                                    'name': 'repo_user_id',
+                                    'description': 'Optional, legacy user id from the old NOMAD CoE repository.',
+                                    'type': {'type_kind': 'python', 'type_data': 'str'},
+                                },
+                                {
+                                    'name': 'is_admin',
+                                    'description': 'Bool that indicated, iff the user the use admin user',
+                                    'type': {
+                                        'type_kind': 'python',
+                                        'type_data': 'bool',
+                                    },
+                                    'virtual': True,
+                                },
+                                {
+                                    'name': 'is_oasis_admin',
+                                    'type': {
+                                        'type_kind': 'python',
+                                        'type_data': 'bool',
+                                    },
+                                    'default': False,
+                                },
+                            ],
+                        },
+                    ],
+                    'category_definitions': [
+                        {'name': 'EntryDataCategory'},
+                        {
+                            'name': 'ElnIntegrationCategory',
+                            'label': 'Third-party ELN Integration',
+                            'categories': ['/category_definitions/0'],
+                        },
+                        {
+                            'name': 'BasicElnCategory',
+                            'label': 'Basic ELN',
+                            'categories': ['/category_definitions/0'],
+                        },
+                        {
+                            'name': 'ElnExampleCategory',
+                            'label': 'Example ELNs',
+                            'categories': ['/category_definitions/0'],
+                        },
+                        {
+                            'name': 'UseCaseElnCategory',
+                            'label': 'Use-cases',
+                            'categories': ['/category_definitions/0'],
+                        },
+                        {
+                            'name': 'WorkflowsElnCategory',
+                            'label': 'Workflows',
+                            'categories': ['/category_definitions/0'],
+                        },
+                    ],
+                    'all_quantities': {
+                        'Author.name': 'metainfo/nomad.datamodel.data/section_definitions/2/quantities/0',
+                        'Author.first_name': 'metainfo/nomad.datamodel.data/section_definitions/2/quantities/1',
+                        'Author.last_name': 'metainfo/nomad.datamodel.data/section_definitions/2/quantities/2',
+                        'Author.email': 'metainfo/nomad.datamodel.data/section_definitions/2/quantities/3',
+                        'Author.affiliation': 'metainfo/nomad.datamodel.data/section_definitions/2/quantities/4',
+                        'Author.affiliation_address': 'metainfo/nomad.datamodel.data/section_definitions/2/quantities/5',
+                        'User.user_id': 'metainfo/nomad.datamodel.data/section_definitions/3/quantities/0',
+                        'User.username': 'metainfo/nomad.datamodel.data/section_definitions/3/quantities/1',
+                        'User.created': 'metainfo/nomad.datamodel.data/section_definitions/3/quantities/2',
+                        'User.repo_user_id': 'metainfo/nomad.datamodel.data/section_definitions/3/quantities/3',
+                        'User.is_admin': 'metainfo/nomad.datamodel.data/section_definitions/3/quantities/4',
+                        'User.is_oasis_admin': 'metainfo/nomad.datamodel.data/section_definitions/3/quantities/5',
+                    },
+                    'all_sub_sections': {},
+                    'all_base_sections': {
+                        'ArchiveSection': 'metainfo/nomad.datamodel.data/section_definitions/0',
+                        'Author': 'metainfo/nomad.datamodel.data/section_definitions/2',
+                    },
+                }
+            },
+            'archive': {
+                'data': {
+                    'm_def': {
+                        'm_def': 'uploads/id_custom/entries/id_example/archive/definitions/section_definitions/0'
+                    }
+                }
+            },
+        },
+    )
+
+    __entry_print(
+        'custom',
+        {
+            Token.ARCHIVE: {
+                'data': {
+                    'm_request': {
+                        'directive': 'plain',
+                    },
+                    'm_def': {
+                        'm_request': {
+                            'directive': 'plain',
+                        },
+                    },
+                }
+            },
+        },
+        result={
             'uploads': {
                 'id_custom': {
                     'entries': {