17. Packages

Packages are your primary mechanism for acquiring and adding features and capabilities to Sublime Text that are not shipped with it. You can acquire Packages from the Sublime Text New Package Control Hub, from 3rd parties, or you can create them yourself if you are so inclined. See Package Distribution below for more information on where and how you can get Packages.

Packages are simply a set of Resource Files used by Sublime Text, gathered into a single directory. That directory’s name defines the Package’s name.

A Package Resource is simply a Resource File that is part of a Package and therefore in a Package directory.

Another (more compact) form they can come in (e.g. the shipped Packages and most user-installed Packages are in this form) is in a .sublime-package file, which is simply a .zip file containing all the files of that package, preserving the Package’s directory structure.

Compressed or not, any package file can be viewed using:

Tools ‣ Command Palette... ‣ View Package File

Files retrieved from a compressed file will be read-only, but may be saved as editable files using.

File ‣ Save As...

(They remain read-only until they are closed and then re-opened again from where they were saved.)

Be advised: if there is a .sublime-package, as well as a loose file in a <data_path>/Packages/pkg_name/ directory, the contents of the loose file will override the ENTIRE file stored in the .sublime-package file. See Overriding below for more details.

Sublime Text ships with over 50 Packages pre-installed, and many, many user-created Packages are available through several channels, the easiest of which is using the Package Control Package, which provides very easy management of the Packages you have installed with any given Sublime-Text installation.

17.1. Sublime Text Packages vs Python Packages

As it turns out, the name “Package” in Sublime Text’s Python context is indeed aptly chosen: from Sublime Text’s perspective, a Package is a Python Namespace Package, which is a Python package which typically has more than one directory containing its contents. This is how Sublime Text Packages can live in multiple directories as is shown below.

In Sublime Text’s Python run-time environment, the <data_path>/Packages/ directory is added to Python’s sys.path list, which provides a set of directories where Python goes looking for a package when it is told:

import xxxxxx

It will look for xxxxxx as a module or directory name. In the case of Sublime Text Packages, it is always a directory name, and can be in a number of locations as explained below.

Note: From your Sublime Text Package, you can do relative importing, which is ONLY legal from within a Python package, and generates an exception when it is used outside of a Python package.

from . import sibling_module

This is also why the <data_path>/User/ directory is sometimes referred to as the “User Package”.

API Environments Overview describes the environment Plugins are run in. The 3rd paragraph describes the relationship of Plugins to the Package.

17.2. Locations

The concept of a “Packages Directory” is really 5 possible locations for any given Package Resource File.

Terms:

Default Package:

Loaded first; Default.sublime-package [1]

  • Represented location: <data_path>/Packages/Default/

  • Actual location: <executable_path>/Packages/

shipped Packages:

Loaded next (alphabetically by Package name); <pkg_name>.sublime-package files shipped with Sublime Text are stored here. (Do not change this directory’s contents.) [1]

  • Represented location: <data_path>/Packages/pkg_name/

  • Actual location: <executable_path>/Packages/

installed Packages:

Loaded next (alphabetically by Package name); Packages installed by user are stored here when they are designed to remain in .sublime-package format. [1]

  • Represented location: <data_path>/Packages/pkg_name/

  • Actual location: <data_path>/Installed Packages/ (not editable)

unpacked Packages:

Loaded next (alphabetically by Package name); Packages installed by the user when designed to be unpacked (loose files) by Package Control, as well as user-created Packages can be stored here.

  • Represented location: <data_path>/Packages/pkg_name/

  • Actual location: same (on disk, Resource files editable)

(See note below.)

User Package:

Loaded last; <data_path>/User/. Customizations unique to each user are stored here. Path representation: Packages/User/.

  • Represented location: <data_path>/Packages/User/

  • Actual location: same (on disk, Resource files editable)

If you are a power user, your customizations will go in the latter 2 directories.

The User Package can be used to develop new packages while they are in experimental stages. Once in a more complete state, they can be moved to <data_path>/Packages/pkg_name/.

17.3. Overriding

The above loading sequence is how “overrides” or customized behavior are implemented. Note carefully that when the Package (including the User Package) has a file like Default (Windows).sublime-keymap or Main.sublime-menu, it does not replace the whole Keymap or Main Menu. In these cases, the overriding happens at a finer level of granularity: these files contain JSON data structures (e.g. a list or tree structure [respectively] of dictionary objects), and the value of each entry in the data structure overwrites the already-loaded value for the entry with the same key and hierarchy location in the data structure. So you can think of the contents of both files as being “merged”, where due to the loading sequence outlined above, the values loaded from the Packages lower in the list overwrite the same values from Packages higher in the list. It is this sequence that governs what overrides what.

Note that the above-described “merge” override behavior is what happens between the Package (including the User Package) and the Sublime Text application, and is restricted to just some (not all) of the Resource Files that contain JSONC format data:

  • .sublime-commands

  • .sublime-completions

  • .sublime-keymap

  • .sublime-menu

  • .sublime-mousemap

  • .sublime-settings

But this does not apply to any of the other types of Resource Files:

  • .sublime-build

  • .sublime-color-scheme

  • .tmTheme

  • .sublime-macro

  • .tmPreferences

  • .py

  • .sublime-snippet

  • .sublime-syntax

  • .tmLanguage

  • .sublime-theme

17.3.1. Overriding Whole Files from a Zipped Package

Once you understand the “merge-type” overriding described above, it is important to understand that there is another type of overriding (whole-file overriding) that is triggered when:

  • the Package being overridden is in archive form (i.e. with .sublime-package file extension, which will be either an installed Package or a shipped Package, and

  • the override file is placed in an unpacked Package directory (i.e. in <data_path>/Packages/pkg_name/ with an identical relative path and name).

In this case, Sublime Text literally ignores that same file from the .sublime-package file and replaces it with loose file in the unpacked Package directory.

In this case, the loose-file directory <data_path>/Packages/pkg_name/ is called an “Override Package”.

Illustrated:

<executable_path>/Packages/<pkg_name>.sublime-package, or
<data_path>/Packages/Installed Packages/<pkg_name>.sublime-package  ----+
                                                                        | loose file with
                                                                        | same name and
                                                                        | same relative
                                                                        | subdirectory
<data_path>/Packages/<pkg_name>/...    <--------------------------------+

When the Override Package directory is empty, it doesn’t change anything. But for each occurrence of a file with the exact same relative subdirectory and filename as in the .sublime-package file, Sublime Edit ignores the .sublime-package file and uses the loose file in the Override Package instead.

An interesting aspect of Override Packages is that Python plugins therein are able to use relative import paths for accessing other modules in the corresponding .sublime-package file as if they were part of it.

Example: You want to add functionality to the Python Package’s def (function) Snippet. Its default content is provided in <executable_path>/Packages/Python.sublime-package.

The way to find the Snippet file that is involved, make a copy of the Python.sublime-package file in a safe, empty directory, change its extension to .zip and then extract the contents out of the file. Scan the package Snippet files for

<tabTrigger>def</tabTrigger> or just >def<

and you will find it in the ./Snippets/ subdirectory as function.sublime-snippet.

Make a copy of that file and place it in

<data_path>/Packages/Python/Snippets/

then modify it to suit you. Sublime Text reloads it as soon as you save it so the turn-around time on modifications-to-testing is remarkably short.

Note

The following two Packages provide a convenient way to create an override of a single Package file. The Commands they provide that do this are:

  • PackageResourceViewer: Open Resource

  • OverrideAudit: Create Override

Both of these commands:

  • open the file in an editable form, with an “apparent” path <data_path>/Packages/pkg_name/resource_file, even though that file (and directory) may not exist yet, and

  • use the Ctrl+S (Save file) operation to create the file if it is not already there, as well as create the new <data_path>/Packages/pkg_name/ directory if it did not already exist.

Note

Some packages are configured to be installed as loose files in the <data_path>/Packages/pkg_name/. When this is the case, once they are installed, the only instance of that Package is as loose files in that directory. When this is the case, editing of those Package Resource files (when they are not created by yourself) is not recommended because those files get replaced when the Package is updated. If you wish to override only certain files in an unpacked Package, you can create a .sublime-package file out of it by zipping its contents, preserving the directory structure under the

<data_path>/Packages/pkg_name/

directory, naming the zipped file

pkg_name.sublime-package

and moving the newly-created file into the

<data_path>/Installed Packages/

directory. Then in the original (unpacked) directory (which now becomes an Override Package because of the existence of the <pkg_name>.sublime-package file where Sublime Text will load it), keep only the files you wish to override.

Caution

A precaution about Override Packages: if/when the overridden file(s) in the corresponding .sublime-package is updated, you will not be notified.

The OverrideAudit package provides monitoring of override files and will notify you when the file it overrides has been updated.

17.4. Creating a New Package

You create a new package by simply creating a directory under <data_path>/Packages/.

This directory is conveniently accessible via Preferences ‣ Browse Packages....

What you put into that directory determines how that Package will supplement Sublime Text’s features and capabilities.

There are a number of different categories of functionality Packages can have. A review of the Package Distribution Hub <Package Control Hub_> shows Packages that:

  • do things (which involve Plugins),

  • supply one or more Completions and/or Snippets files,

  • supply a new language syntax (involves color highlighting),

  • supply a Build System,

  • supply a Color Scheme,

  • supply a Theme,

  • supply reusable libraries for other Package developers, and

  • combinations of the above.

If you plan to share your Package with the Sublime Text community, some functionality overlap is understandably restricted by Submitting a Package, but you can always make more than one Package.

17.4.1. Python Version

As of Sublime Text build 4200, the default Python version used by a package is 3.3. However, Python 3.8 is available. To get your Package to use Python 3.8, place a file in the top directory of the package containing just 3 bytes: “3.8”.

cd MyPackage/
ls .py*
.python-version
cat .python-version
3.8

17.4.2. Managing Complexity

As functionality is added to Packages being developed, they can become quite complex, or perhaps they start off having a complex design right from the beginning. For increased maintainability it can be useful to move related groups of related functionality into their own Python modules for the same reason any complex Python program is broken down into modules.

Simpler Packages can factor out related functionality into their own sibling (top-level) Python modules which make use of each others’ facilities via the from . import sibling_module statement.

More complex Packages sometimes factor out related functionality as Subpackages for the same reason that Python supports Subpackages within Packages: modularity and maintainability—each Subpackage and each Python module has its own sphere of responsibility, which reduces complexity within each Subpackage. The method of doing this is exactly the same as implementing any Python Subpackage. Usually developers will want to limit doing this to when it makes sense to do so: when the subpackage is relatively self-contained, providing its own data and/or services to the main Package, and (preferably) only depending upon external Packages that ship with Python.

17.4.2.1. Caveat for Subpackages

There is an important caveat to using Subpackages in a Sublime Text Package if you have the Package Control Package installed (which is true for most Sublime Text users). The reason for this is that the Package Control Package automatically updates installed Packages while Sublime Text is running, and when it does this, it does not re-start the Python Interpreter, but instead uses importlib.reload() to reload the top-level Python modules (.py files) in each installed Package that gets updated. If an updated Package has Subpackages that might have been updated, those Subpackages do not get automatically reloaded (with their possibly-updated logic) until Sublime Text restarts. For Packages that will never change again, this is not an issue. However, for packages that get updated periodically, when the update happens, until Sublime Text is restarted, there is a risk of such Packages being in an inconsistent state and possibly broken unless the Package itself addresses this caveat internally.

Here is how to do it.

Let’s say your Package is called ProComment and is structured like this:

ProComment/
    procomment.py         <-- central Plugin
    lib/
        __init__.py
        debug.py
        utils.py
    src/
        __init__.py
        core.py
        contexts.py
        func_parser.py
        commands/
            __init__.py
            insert_comment.py
            insert_datestamp.py
            insert_timestamp.py

Your central (top-level) Plugin is named procomment.py and you have 3 Subpackages: lib, src/ and src/commands/. In your central Plugin, implement something like this:

""" procomment.py """
import importlib
import sys
import os
from typing import Tuple


# =========================================================================
# Data
# =========================================================================

_cfg_compressed_pkg_ext = '.sublime-package'

# Use name of parent directory as `package_name`.
module_path, _ = os.path.splitext(os.path.realpath(__file__))
parent_dir, submodule_name = os.path.split(module_path)
_, package_name = os.path.split(parent_dir)
if package_name.endswith(_cfg_compressed_pkg_ext):
    package_name = package_name[:-len(_cfg_compressed_pkg_ext)]
del _
this_module_name = f'{package_name}.{submodule_name}'
_reload_indent_level = -1

# Can't use `debugging = is_debugging(DebugBits.IMPORTING)` here because
# the import required to support it causes a circular import.
debugging = True
if debugging:
    print(f'{this_module_name}  >>> module execution')


# =========================================================================
# Load / Reload
# =========================================================================

def reload(dotted_subpkg: str, submodules: Tuple[str, ...] = ()):
    """
    Reload each module in `submodules` only if previously loaded.  This is a
    precondition of calling ``importlib.reload()`` but is also for efficiency:

    - if Sublime Text is just starting, nothing important happens here (because
      the cached modules will not have been added to ``sys.modules`` yet), and
      and the various ``import`` statements do the loading in the usual way;

    - if ``Package Control`` is updating this Package (or the central Plugin
      was just saved during development), then this function recursively
      reloads each loaded module, and the ``import`` statements then do
      nothing since each target module will already be in ``sys.modules``.

    Note:  The below works on the basis that :file:`<sublime_data>/Packages`
           directory was placed in ``sys.path`` by Sublime Text.  So the
           module names being constructed below have to look like this:

               MyPackage.subdir.module
               MyPackage.subdir.subdir.module
               etc.

    :param dotted_subpkg:  dotted directory portion of module names that
                             will be found in the keys of ``sys.modules``.
                             Example:  'MyPackage.src.commands'
    :param submodules:     tuple of submodule names; CAUTION: if there
                             is just one submodule, ('module_name') is NOT
                             NOT A TUPLE and will cause that module to NOT
                             be reloaded!  It must be ('module_name',) with
                             a comma to make it a tuple.
    """
    global _reload_indent_level
    _reload_indent_level += 1
    indent = '  ' * _reload_indent_level
    if debugging:
        if _reload_indent_level == 0:
            print('vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv')
        print(f'{indent}reload():  >>> {dotted_subpkg=} {submodules=}')

    if not submodules:
        # Called from top-level Plugin.
        module_name = dotted_subpkg
        if module_name in sys.modules:
            if debugging:
                print(f'{indent}Reloading({module_name})')
            importlib.reload(sys.modules[module_name])
    else:
        # Called from subpackage.
        for submodule in submodules:
            module_name = f'{dotted_subpkg}.{submodule}'
            if module_name in sys.modules:
                if debugging:
                    print(f'{indent}Reloading({module_name})')
                importlib.reload(sys.modules[module_name])

    if debugging:
        print(f'{indent}reload():  <<<')
        if _reload_indent_level == 0:
            print('^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^')

    _reload_indent_level -= 1


reload(package_name + '.lib')  # Recurse into .lib/ subpackage.
reload(package_name + '.src')  # Recurse into .src/ subpackage.

from .lib import *
from .src import *


# =========================================================================
# Events
# =========================================================================

def plugin_loaded():
    core.on_plugin_loaded()


def plugin_unloaded():
    core.on_plugin_unloaded()


if debugging:
    print(f'{this_module_name}  <<<')

Then in each __init__.py file for each Subpackage:

""" lib/__init__.py """
from ..procomment import reload
from .debug import IntFlag, DebugBits, is_debugging

debugging = is_debugging(DebugBits.IMPORTING)
if debugging:
    print(f'{__package__}  >>> module execution')

reload(__package__, ('debug', 'utils'))

from . import utils

__all__ = [
    'debug',
    'utils'
]

if debugging:
    print(f'{__package__}  <<<')
""" src/__init__.py """
from ..procomment import reload
from ..lib.debug import IntFlag, DebugBits, is_debugging

debugging = is_debugging(DebugBits.IMPORTING)
if debugging:
    print(f'{__package__}  >>> module execution')

reload(__package__, ('core', 'contexts', 'func_parser'))
reload(__package__ + '.commands')  # Recurse into .commands/ subpackage.

from . import core
from .contexts import *
from .commands import *

__all__ = [
    'core',
    'func_parser',

    # events/contexts
    "ProCommentContextEventListener",

    # commands/*
    "ProCommentInsertDatestampCommand",
    "ProCommentInsertTimestampCommand",
    'ProCommentInsertCommentCommand',
]

if debugging:
    print(f'{__package__}  <<<')
""" src/commands/__init__.py """
from ...procomment import reload
from ...lib.debug import IntFlag, DebugBits, is_debugging

debugging = is_debugging(DebugBits.IMPORTING)
if debugging:
    print(f'  {__package__}  >>> module execution')

reload(__package__, ('insert_datestamp', 'insert_timestamp', 'insert_comment'))

from .insert_comment import ProCommentInsertCommentCommand
from .insert_datestamp import ProCommentInsertDatestampCommand
from .insert_timestamp import ProCommentInsertTimestampCommand

__all__ = [
    # Comment Commands
    'ProCommentInsertCommentCommand',

    # Datestamp/Timestamp Commands
    'ProCommentInsertDatestampCommand',
    'ProCommentInsertTimestampCommand',
]

if debugging:
    print(f'  {__package__}  <<<')

Note

The __package__ variable for each Package contains the “package name” as it is known by the Python run time, specifically sys.modules and Python’s import mechanism. For the source Package represented by src/commands/__init__.py, for example, it is this string: ProComment.src.commands—which is exactly what the reload() function needs in its first argument.

Note carefully that src/__init__.py explicitly recurses into the src/commands/ directory like this—after all its modules at that level were reloaded:

reload(__package__ + '.commands')  # Recurse into .commands/ subpackage.

This pattern then causes a “cascade” of all the modules being reloaded: when procomment.py gets either loaded (for the first time), or reloaded (by Package Control, or saved during development).

The end result of this pattern is:

  • When Sublime Text starts, all Subpackages get loaded via a cascade of import statements.

  • When the Package Control Package updates your Package and reloads the central Plugin (or when you save your central Plugin during development), all the modules in each Subpackage get reloaded via a cascade of reload() calls.

If you would like to verify/trace that visually, you can see the cascade of importing and reloading in the debug output (from the code above) into the Console Panel when Sublime Text is first started. This is when the loading is mostly being done by import statements:

reloading plugin ProComment.procomment
ProComment.procomment  >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload():  >>> dotted_subpkg='ProComment.lib' submodules=()
reload():  <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload():  >>> dotted_subpkg='ProComment.src' submodules=()
reload():  <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ProComment.lib  >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload():  >>> dotted_subpkg='ProComment.lib' submodules=('debug', 'utils')
Reloading(ProComment.lib.debug)
reload():  <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ProComment.lib  <<<
ProComment.src  >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload():  >>> dotted_subpkg='ProComment.src' submodules=('core', 'contexts', 'func_parser')
reload():  <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload():  >>> dotted_subpkg='ProComment.src.commands' submodules=()
reload():  <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ProComment.src.commands  >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload():  >>> dotted_subpkg='ProComment.src.commands' submodules=('insert_datestamp', 'insert_timestamp', 'insert_comment')
reload():  <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ProComment.src.commands  <<<
ProComment.src  <<<
ProComment.procomment  <<<

And this is the debug output each time procomment.py is saved during development, or when the Package Control Package updates the package while Sublime Text is running). This is when the loading is being done entirely by the cascade of calls to the reload() function:

reloading plugin ProComment.procomment
ProComment.procomment  >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload():  >>> dotted_subpkg='ProComment.lib' submodules=()
Reloading(ProComment.lib)
  reload():  >>> dotted_subpkg='ProComment.lib' submodules=('debug', 'utils')
  Reloading(ProComment.lib.debug)
  Reloading(ProComment.lib.utils)
  reload():  <<<
reload():  <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload():  >>> dotted_subpkg='ProComment.src' submodules=()
Reloading(ProComment.src)
ProComment.src  >>> module execution
  reload():  >>> dotted_subpkg='ProComment.src' submodules=('core', 'contexts', 'func_parser')
  Reloading(ProComment.src.core)
  Reloading(ProComment.src.contexts)
  Reloading(ProComment.src.func_parser)
  reload():  <<<
  reload():  >>> dotted_subpkg='ProComment.src.commands' submodules=()
  Reloading(ProComment.src.commands)
  ProComment.src.commands  >>> module execution
    reload():  >>> dotted_subpkg='ProComment.src.commands' submodules=('insert_datestamp', 'insert_timestamp', 'insert_comment')
    Reloading(ProComment.src.commands.insert_datestamp)
    Reloading(ProComment.src.commands.insert_timestamp)
    Reloading(ProComment.src.commands.insert_comment)
    reload():  <<<
  ProComment.src.commands  <<<
  reload():  <<<
ProComment.src  <<<
reload():  <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ProComment.procomment  <<<

When you turn debugging output off, and all of the above happens silently and happily in the background.

Tip

Don’t have a module in a subpackage inherit a sibling module through a relative path like this (example uses a module in the ./lib/ subpackage)

from ..lib inherit sibling_module

Reason: before the lib module is fully imported from a parent module, that module will try to import the lib module again through the relative path, and this causes the importing execution path to get very complicated very quickly.

Policy: ensure sibling modules import from each other without involving the containing subpackage again, e.g. by using a relative path like this instead.

from . inherit sibling_module

17.5. Sharing Your Package

Sharing your Package with the Sublime Text community in most cases involves getting it into the “umbrella” of Packages known to the PackageControl Package, which includes a live submission point, and a live distribution hub that is currently maintained by the ST community.

  1. Study and follow the instructions at Submitting a Package.

  2. The submission point is at Package Control Public Channel.

  3. The distribution is accomplished by Package Control Hub.

17.5.1. Package Distribution

In theory, the above provides the core of what MUST be done (file locations) in order to get a package into use in Sublime Text. Any mechanism, including manually adding and removing Packages, will work.

However, there is a marvellous facility for Package Distribution implemented through the Package Control Package: it has a nifty scheme that adds flexibility and ease of access to a large library of 3rd-party Sublime Text Packages.

The simple view is:

You don’t need to know how it works. Just install the Package Control Package and use its “install” and “uninstall” commands to deal with Sublime Text Packages you want.

If you are a Package developer, follow the instructions in the Submitting a Package page to make your Package available to the world-wide community of Sublime Text users.

But if you want some insight as to how it works, in order to know how to take advantage of its other features, and extend your reach to additional collections of Sublime Text Packages, and/or host your own private Package collection, e.g. to supply a secure set of Packages within an enterprise, read on.

The original Package Control server is https://packagecontrol.io/. It was from this server that the original versions of the Package Control Package read the information and content, enabling users to conveniently install and uninstall packages with just a few keystrokes.

However, its original design had a few shortcomings that made it problematic to deal with in certain cases. Since this facility was already well embedded and heavily used by the community of Sublime Text users, its design could not be changed easily, and new technology had become available that the Package Control team wanted to take advantage of. So they created a 2nd platform that they built to host the new distribution technology. And so there is a 2nd “distribution hub” now https://packages.sublimetext.io/ and both servers are active, as the team evolves the Package Control distribution infrastructure. As of April 2026, the plan (as described by @milkman on Sublime Text Forum) is that once development is complete, to move the new technology and infrastructure developed in the new distribution hub back to the original hub again.

Meanwhile, don’t be confused about there being 2 active hubs that appear to have the same content: they do!

Because of the new infrastructure, if you are developing a Package and want to share it to the community, the documentation to follow is the Submitting a Package page, not the Original Submitting a Package page.

17.5.1.1. Distribution Data Tree

From the PackageControl Package’s perspective on your installation of Sublime Text, it gets its data (including lists of available packages, etc.) from a data tree. This is what it looks like.

At this writing there are several thousand 3rd-party Sublime Text Packages that live on thousands of different hosting servers, including a large number of both public and private Git repositories.

How does one Package make that list available to a user from

Preferences > Package Control > Package Control: Install Package

in a list that appears almost instantly?

The answer required a bit of structure to be placed on a fairly large “tree” of data.

It appears that the original concept of the tiers in this tree of data started off like this (the “crows foot” shows a 1-to-many relationship):

 Channel    (information path, such as public, enterprise, etc.)
 -------
    |
    |
   /|\
Repository  (collection of Packages related by topic, author(s) or team)
----------
    |
    |
   /|\
 Package    (an individual Python Package that extends Sublime Text functionality)

and this appears to have been a starting point. But as you will see, something in the Package Control Package logic allows the the “nodes” at the various tiers in this tree (Channel, Repository, Package) to have a bit of “fuzziness” in what they actually contain. For example, a “node” at the “Repository tier” can be:

  • a repository.json file

  • a packages.json file

  • a URL to a Git user account (GitHub, GitLab, BitBucket)

  • a URL to a an individual Git repository

  • a URL to a reference (e.g. tag or branch) within a Git repository

See Package Control.sublime-settings via

Preferences > Package Settings > Package Control > Settings

for the URL syntax of how to include new repositories in the “repositories” list.

And in the last 2 items in the above list, the astute reader will note that the “Repository” and “Package” tier got “blended together” since an individual Git repository is expected to host a single Package.

At the “Package” tier is the “leaf” object, which can be either of these two things:

  • a JSON “package descriptor” object, which both describes an individual Package as well as points to where its actual content can be found, or

  • the actual Package Resource Files (where the “Repository” item above it is an individual Git repository, or tag or branch).

Furthermore, the astute reader might have explored

Preferences > Package Settings > Package Control > Settings

and discovered that one of the items in the “channels” setting (a list) is

which literally contains all 3 tiers in the above data tree, presumably for efficiency (only one 4MB download is involved).

So exactly how the Package Control package navigates these tiers is not known to this author (yet), but I will say this: it’s clever!

And it gets an installable list of Sublime Text Packages onto the user’s screen nearly instantly.

And that took some doing!

17.5.1.2. Flow of Package Data

Package Source:

A Package author develops and tests his Package and makes it ready for end-user consumption by hosting it in his own Git repository containing the Package Resource files.

Package Control Source:

That Package author follows the instructions at Submitting a Package and submits a Package Descriptor JSON object describing his Package in exactly 1 JSON file in the ./repository/ directory at Package Control Public Channel where you can see the above data structure at work:

./channel.json
    +-- (currently) 114 additional URLs pointing to other ``packages.json``
    |     format files hosted on different websites, most notably GitHub.
    +-- ./repository.json (hosted in THIS Git repository)
           +-- ./repository/0-9.json
           +-- ./repository/a.json
           +-- ./repository/b.json
           +-- ./repository/c.json
           +-- ...
           +-- ./repository/m.json
           |      +-- (currently) 251 JSON Package descriptor objects
           +-- ...
           +-- ./repository/y.json
           +-- ./repository/z.json
Distribution Hubs:

This is where the data from the above is regularly web-crawled for updates and content is processed, generated and packaged ready for download by the Package Control Package.

Packages repositories and libraries (currently schema version 4) are at

https://packages.sublimetext.io/channel.json

This one file contains everything needed to give the Package Control Package access to the entire list of publicly-hosted 3rd-party Packages and libraries available to Sublime Text.

See also: https://packages.sublimetext.io/about

Part of the beauty of all this is that the Package author can now submit updates to his package by simply pushing them to his master branch in his Git repository and when he’s ready for the next release version, he simply adds a new semantic version tag according to Submitting a Package, and the above infrastructure takes it from there, making a new downloadable .sublime-package file ready for download, and Package Control Packages on individual workstations all over the world take care of updating each user’s workstation! Brilliant!

17.5.1.3. Version Tag

You can push additional updates to the master branch, and the READMEs will get updated in Package distribution hubs:

but no files actually make it to end users until you place another (more recent) semantic version tag on the master branch.

Note

The GitFlow branch pattern is recommended for managing your Package’s repository. With it, the master branch only gets updated from official releases, and it is very versatile and forgiving when you have software “in the field”.

17.5.1.4. Current State of Package Control

Official Status: https://packages.sublimetext.io/about

This is from @milkman (on Sublime Text Forum) about some better understandings of Package Control and what is currently going on – why there are 2 Package distribution hubs and both are running.

Regarding packges.sublimetext.io, there’s a bit of history documented: https://packages.sublimetext.io/about . tldr; It aims to replace packagecontrol.io with newer technology (in order to work around some existing issues with the older original packagecontrol.io <Original Package Control Hub>) and is being maintained by a couple of community members.

From a user perspective, its equivalent to packagecontrol.io, but could work better in some cases. The future plan is that SublimeHQ maintains an “official” package platform (likely with the same infrastructure that packges.sublimetext.io uses today), completely replacing packges.sublimetext.io. I guess no one knows, what the future of packges.sublimetext.io will be then.

Well, that was longer than expected :sweat_smile:

(Disclaimer: That’s my personal summary of the website situation.)

This is from @deathaxe (on Sublime Text Forum) on the same question:

The package_control_channel repository basically provides a template.

Technically, Package Control could directly use package_control_channel, but fetching required information for all packages from code hosters is rather expensive. As PC was created before asyncio arrived, it is not really designed to perform heavy parallel REST API access work.

Hence crawlers like (packagecontrol.io or thecrawl) do the hard job of fetching up-to-date package information from code hosters and convert all “release” entries to URL-based releases with directly downloadable packages. So PC just needs to load a single JSON file and can directly download requested packages.

Technically, those crawlers would just need to provide a very simple repository.json with all package entries only containing those URL based releases. We’d just need to replace “channels” setting by “repositories” to point to another URL and would be done.

A channel in it’s original form is just a collection of repositories. This could be achieved using “include” in a repository as well. But those probably came later.

A repository is just a collection of packages and/or libraries (aka. python packages).

The channel downloaded by PC is exactly the channel.json from package_control_channel repository with package_cache and dependencies_cache entries being added by crawlers containing package meta data with resolved URLs. Overcomplicated, don’t ask anyone why this choice was made.

17.5.1.5. More About Package Control

https://docs.sublimetext.io/guide/package-control/usage.html

17.6. Further Reading

The more you know about Python’s Import System, the more you will understand some of the power of Sublime Text Packages and their facilities brought to bear by Python itself (e.g. their ability to use modular subpackages to cleverly manage Package complexity, and thereby make a Package more understandable, and thereby more maintainable).

See also: