diff --git a/Makefile.am b/Makefile.am
index c668b96a37..8e400c69c8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -20,6 +20,8 @@
 # Copyright © 2023 Clément Lassieur <clement@lassieur.org>
 # Copyright © 2023, 2024 Wilko Meyer <w@wmeyer.eu>
 # Copyright © 2024 gemmaro <gemmaro.dev@gmail.com>
+# Copyright © 2023 Wilko Meyer <w@wmeyer.eu>
+# Copyright © 2023, 2025 Kierin Bell <fernseed@fernseed.me>
 #
 # This file is part of GNU Guix.
 #
@@ -552,6 +554,7 @@ SCM_TESTS =					\
   tests/elm.scm				\
   tests/elpa.scm				\
   tests/file-systems.scm			\
+  tests/formatters/elisp.scm			\
   tests/gem.scm				\
   tests/gexp.scm				\
   tests/git.scm					\
@@ -565,6 +568,7 @@ SCM_TESTS =					\
   tests/hexpm.scm				\
   tests/home-import.scm				\
   tests/home-services.scm			\
+  tests/home/services/emacs.scm			\
   tests/http-client.scm				\
   tests/import-git.scm				\
   tests/import-github.scm			\
diff --git a/doc/guix.texi b/doc/guix.texi
index 6844470ce2..8b552dfef2 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -138,6 +138,7 @@
 Copyright @copyright{} 2025 Sören Tempel@*
 Copyright @copyright{} 2025 Rostislav Svoboda@*
 Copyright @copyright{} 2025 Zacchaeus@*
+Copyright @copyright{} 2023-2025 Kierin Bell@*
 
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -462,6 +463,7 @@ Top
 * SSH: Secure Shell.            Setting up the secure shell client.
 * GPG: GNU Privacy Guard.       Setting up GPG and related tools.
 * Desktop: Desktop Home Services.  Services for graphical environments.
+* Emacs: Emacs Home Services.   Services for configuring Emacs.
 * Guix: Guix Home Services.     Services for Guix.
 * Fonts: Fonts Home Services.   Services for managing User's fonts.
 * Sound: Sound Home Services.   Dealing with audio.
@@ -47660,6 +47662,7 @@ Home Services
 * SSH: Secure Shell.            Setting up the secure shell client.
 * GPG: GNU Privacy Guard.       Setting up GPG and related tools.
 * Desktop: Desktop Home Services.  Services for graphical environments.
+* Emacs: Emacs Home Services.   Services for configuring Emacs.
 * Guix: Guix Home Services.     Services for Guix.
 * Fonts: Fonts Home Services.   Services for managing User's fonts.
 * Sound: Sound Home Services.   Dealing with audio.
@@ -49174,6 +49177,1158 @@ Desktop Home Services
 @command{startx} used.  Default value is @code{(xorg-configuration)}.
 @end defvar
 
+@node Emacs Home Services
+@subsection Emacs Home Services
+
+The @code{(gnu home services emacs)} module provides services for
+configuring the GNU Emacs extensible text editor.
+
+@cindex Elisp expressions, for Emacs Home services
+Emacs is configured by providing @dfn{initialization files} that contain
+@dfn{s-expressions} written in @dfn{Emacs Lisp} (abbreviated as
+@dfn{Elisp}) which are evaluated when Emacs is started (@pxref{Init
+File,,, emacs, The GNU Emacs Manual}).
+
+The @code{(gnu home services emacs)} module introduces a mechanism for
+specifying s-expressions that should be serialized as Elisp and
+evaluated by Emacs: @dfn{Elisp expressions}.  Elisp expressions have
+their own data type (see @code{elisp?}), and they must be created by
+using @code{elisp} forms, or by using other functions provided by the
+module for constructing them.  Whenever the term ``Elisp expression''
+occurs in this documentation, it is an indication that Elisp expressions
+of this type should be used.
+
+In many ways, Elisp expressions are similar to G-expressions
+(@pxref{G-Expressions}).  Elisp expressions can in fact be thought of as
+an abstraction around G-expressions.  After all, before any Elisp
+expression can be serialized to a file by a service, it must first be
+transformed into a G-expression so that a derivation can be generated
+(@pxref{Derivations}).
+
+For this reason, any value that is a valid input for a G-expression can
+be referenced within an Elisp expression (see @code{unelisp} and
+@code{unelisp-splicing} below).  Data types that ``compile'' and are
+specially substituted in G-expressions, such as file-like objects
+(@pxref{G-Expressions, file-like objects}), will be substituted in the
+same exact way when they are referenced within Elisp expressions.  Even
+G-expressions themselves can be embedded within Elisp expressions.
+
+On the other hand, when Elisp expressions are referenced manually within
+G-expressions (e.g., with @code{ungexp}), some of the expressive power
+of Elisp expressions is lost, as explained below: comments, newlines,
+and page-breaks are stripped.
+
+@defmac elisp @var{exp}
+Return an Elisp expression containing @var{exp}.
+
+Expressions within @var{exp} are constants rather than expressions that
+are evaluated for their Scheme values---as if the expressions were
+quoted using the @code{quote} syntax---unless they are ``unquoted'' with
+one of the following two forms:
+
+@table @code
+@item (unelisp @var{exp})
+Include the value of @var{exp} in an @code{elisp} form.
+
+@var{exp} is an s-expression, given in Scheme, that is evaluated, and
+the resulting value is included within the containing form.  Any values
+that may appear within G-expressions are valid, and any substitutions
+that would be made when ``compiling'' a G-expression will also be made
+when the resulting Elisp expression is serialized to a file.
+
+If the result of evaluating @var{exp} is a list, it is traversed and all
+relevant substitutions are similarly performed.
+
+If the result of evaluating @var{exp} is another Elisp expression, its
+contents are inserted, with the relevant references included as above.
+
+@item (unelisp-splicing @var{lst})
+Like the above, but splices the contents of @var{lst} inside the
+containing expression (which must itself be a list).
+@end table
+
+Additionally, the following forms allow for the inclusion of comments
+and whitespace into Elisp expressions:
+
+@table @code
+@item (unelisp-comment @var{comment})
+Insert a comment containing the string @var{comment} into the containing
+expression.
+
+@var{comment} must be a proper string that begins with @samp{;} and ends
+with a newline character.
+
+When the containing Elisp expression is serialized to an Elisp file (see
+@code{elisp-file}), the comment is pretty-printed as it occurs.
+However, when an Elisp expression is referenced within a G-expression
+manually (e.g., using the @code{ungexp} syntax), all comments specified
+with these forms are lost.  This is because comments cannot normally be
+``compiled'' into a substitution while lowering a G-expression.
+
+@item (unelisp-newline)
+Insert a newline into the containing expression.
+
+When an Elisp expression is serialized to an Elisp file, newlines are
+inserted where they occur.  But, as with @code{unelisp-comment},
+newlines specified using this syntax are removed when an Elisp
+expression is referenced manually within a G-expression.
+
+@item (unelisp-page-break)
+Insert a page-break character into the containing expression.
+
+When an Elisp expression is serialized to an Elisp file, page-break
+characters are inserted where they occur, but, again, they are removed
+when an Elisp expression is manually referenced within a G-expression.
+@end table
+@end defmac
+
+Each Elisp expression can only contain a single s-expression.  To
+specify multiple s-expressions, we must use a list of Elisp expressions.
+The macro @code{elisp*} exists to do this more succinctly.
+
+For instance, this:
+
+@lisp
+(list
+ (elisp (require 'window))
+ (elisp (defun my--get-other-window ()
+          (get-mru-window nil nil t)))
+ (elisp (setopt other-window-scroll-default
+                (function my--get-other-window))))
+@end lisp
+
+@noindent
+is equivalent to this:
+
+@lisp
+(elisp*
+ (require 'window)
+ (defun my--get-other-window ()
+   (get-mru-window nil nil t))
+ (setopt other-window-scroll-default
+         (function my--get-other-window)))
+@end lisp
+
+@defmac elisp* @var{exps} @dots{}
+Return a list of Elisp expressions containing @var{exps}.
+@end defmac
+
+@deffn {Procedure} elisp? obj
+Return true if @var{obj} is an Elisp expression object.
+@end deffn
+
+@deffn {Procedure} elisp->sexp exp
+Return an s-expression containing the contents of Elisp expression
+@var{exp}.
+@end deffn
+
+@deffn {Procedure} sexp->elisp sexp
+Return an Elisp expression object containing @var{sexp}.
+@end deffn
+
+@cindex Elisp files, for Emacs Home services
+Once we have some Elisp expressions, we need to be able to serialize
+them to an Elisp file.  Usually, we provide Elisp expressions within
+configuration fields for Emacs Home services, which automatically
+serialize them to the appropriate Emacs initialization files.  However,
+we can also serialize Elisp expressions directly to arbitrary files
+ourselves.  The @code{elisp-file} procedure takes Elisp expressions and
+returns a file-like object ensuring that the expressions will be
+pretty-printed as Elisp---comments, newlines and all.
+
+@deffn {Procedure} elisp-file name exps [#:special-forms ()]
+Return an object representing the store file @var{name}, an Emacs Lisp
+file that contains @var{exps}, a list of Elisp expression objects or
+G-expressions.
+
+Custom indentation rules can be specified with @var{special-forms}, an
+association list where each entry is of the form:
+
+@lisp
+(@var{symbol} . @var{indent})
+@end lisp
+
+@noindent
+When @var{symbol} occurs at the beginning of a list in an expression in
+@var{exps}, the first @var{indent} expressions after @var{symbol} are
+indented as arguments and the remainder are indented as body
+expressions, as if @var{indent} was the value of the
+@code{lisp-indent-function} symbol property for @var{symbol} in Emacs
+(@pxref{Indenting Macros,,,elisp,The Emacs Lisp Manual}).  As in Emacs,
+argument expressions, if they cannot be pretty-printed on the same line
+as @var{symbol}, are indented 4 columns beyond the base indentation of
+the enclosing list, and body expressions are indented 2 columns beyond
+the base indentation.
+
+This is the declarative counterpart of @code{elisp-file*}.
+@end deffn
+
+@deffn {Procedure} elisp-file* name exps [#:special-forms ()]
+Return as a monadic value a derivation that builds an Elisp file named
+@var{name} containing the expressions in @var{exps}, a list of Elisp
+expression objects or G-expressions.
+
+This is the monadic counterpart of @code{elisp-file}, which see for a
+description of @var{special-forms}.
+@end deffn
+
+@deffn {Procedure} elisp-file? obj
+Return true if @var{obj} is an Elisp file object.
+@end deffn
+
+There are two service types for configuring Emacs.  The fundamental
+service type for configuring Emacs is the
+@code{home-emacs-service-type}.  The
+@code{home-emacs-packages-service-type} extends the former by providing
+a useful layer of abstraction for the configuration of Emacs packages
+and any associated options and keybindings in Scheme.
+
+@cindex Emacs, Home service
+@cindex Emacs, configuration
+@defvar home-emacs-service-type
+This is the basic service type for configuring Emacs.  Its value is a
+@code{home-emacs-configuration} object.
+
+Here is a sample Guix Home configuration that utilizes the
+@code{home-emacs-service-type}:
+
+@lisp
+(use-modules (gnu home)
+             (gnu services)
+             (guix gexp)
+             (gnu home services emacs)
+             (gnu packages emacs-xyz))
+
+(home-environment
+  (services
+   (list
+    (service
+     home-emacs-service-type
+     (home-emacs-configuration
+      (user-emacs-directory "~/.local/state/emacs/")
+      (packages (list emacs-orderless))
+      (init-file
+       (cons*
+        (elisp
+         (unelisp-comment
+          ";; Copyright (C) 2025 Free Software Foundation, Inc.\n"))
+        (local-file "my-init.el")   ; Include contents of a file.
+        (elisp*
+         (setopt confirm-kill-emacs 'y-or-n-p
+                 visible-bell #t
+                 initial-scratch-message #f)
+         (keymap-global-set "C-x q" (function bury-buffer)
+                            "C-x C-b" (function ibuffer))
+         (setq-default indent-tabs-mode nil)
+         (tool-bar-mode -1)
+         (delete-selection-mode 1)
+         (setopt completion-styles '(orderless basic)
+                 completion-category-overrides
+                 '((file (styles basic partial-completion)))))))
+      (servers
+       (list
+        (emacs-server
+         (name "main")
+         (environment-variables
+          #~(cons* #$(string-append "WAYLAND_DISPLAY="
+                                    (or (getenv "WAYLAND_DISPLAY")
+                                        "wayland-1"))
+                   #$(string-append "DISPLAY="
+                                    (or (getenv "DISPLAY") ":0"))
+                   (default-environment-variables)))))))))))
+@end lisp
+
+@noindent
+The configuration above will install and configure Emacs, additionally
+installing and configuring the @code{emacs-orderless} package.
+
+This will configure Emacs by creating an @file{init.el} file in the
+Emacs configuration directory that has a copyright header comment, the
+contents of the @file{my-init.el} file, and Elisp s-expressions that set
+up some variables, keybindings, and global minor modes.  Additionally,
+it will set the Emacs user directory to @file{~/.local/state/emacs/},
+doing some things behind the scenes to ensure that any variables set
+during the Emacs early initialization process are updated appropriately.
+Finally, it will start an Emacs server called ``main'' using a Shepherd
+Home service (@pxref{Shepherd Home Service}), and set up some
+environment variables to ensure that the Emacs server works as expected.
+
+The @code{home-emacs-service-type} can be extended using the
+@code{home-emacs-extension} record type.  Here is an example:
+
+@lisp
+...
+(define %gnus-init-file
+        (elisp-file
+         "gnus.el"
+         (elisp*
+          (setopt gnus-select-method '(nnnil "")
+                  gnus-secondary-select-methods '((nnml ""))
+                  mail-sources
+                  '((imap :server "mail.example.net"
+                          :user "user@@example.net"
+                          :port 993
+                          :stream tls))))))
+
+(home-environment
+ (services
+  (list
+   ...
+   (simple-service
+    'emacs-mail-service
+    home-emacs-service-type
+    (home-emacs-extension
+     (init-file
+      (elisp*
+       (setopt gnus-init-file (locate-user-emacs-file "gnus.el")
+               mail-user-agent 'gnus-user-agent
+               read-mail-command (function gnus)
+               smtpmail-servers-requiring-authorization
+               "mail\\.example\\.net"
+               smtpmail-smtp-server "mail.example.net"
+               smtpmail-smtp-service 587
+               smtpmail-smtp-user "user@@example.net"
+               message-send-mail-function (function smtpmail-send-it)
+               message-signature-file
+               (locate-user-emacs-file "signature"))))
+     (user-files
+      `(("gnus.el" . ,%gnus-init-file)
+        ("signature" . ,(local-file "signature")))))))))
+@end lisp
+
+@noindent
+The configuration above extends the Emacs home service by configuring
+the Emacs Gnus Newsreader (@pxref{Top,,,Gnus,The Gnus Newsreader}) and
+the @code{smtpmail} and @code{message} packages so that Emacs can send
+and receive mail.
+
+Note that some users may prefer to configure these packages using the
+the @code{home-emacs-packages-service-type}, which see for an example of
+such a configuration.
+@end defvar
+
+The record types for configuring the Emacs Home service type are
+described in detail below.
+
+@deftp {Data Type} home-emacs-configuration
+Available @code{home-emacs-configuration} fields are:
+
+@table @asis
+@item @code{emacs} (default: @code{emacs}) (type: package)
+The package providing the @file{/bin/emacs} command.
+
+@item @code{packages} (default: @code{()}) (type: list-of-file-likes)
+A list of additional Emacs-related packages to install.
+
+@item @code{user-emacs-directory} (default: @code{"~/.config/emacs/"}) (type: string)
+Directory beneath which additional Emacs user files are placed.  By
+default, this is also the directory that contains the @file{init.el} and
+@file{early-init.el} Emacs initialization files, but you can change this
+field to specify any directory of your choosing; initialization files
+generated by this service will still be loaded.
+
+@item @code{native-compile?} (default: @code{#f}) (type: boolean)
+Whether to enable native-compilation of Emacs packages by building them
+with the Emacs specified by the @code{emacs} field rather than
+@code{emacs-minimal}.
+
+@item @code{indent-forms} (default: @code{()}) (type: alist)
+An association list of symbols and indentation rules.  Each entry is of
+the form (@var{symbol} .  @var{indent}), where @var{symbol} is a symbol
+and @var{indent} is an integer.
+
+When @var{symbol} occurs at the beginning of a list in an Emacs Lisp
+file, the first @var{indent} expressions are indented as arguments and
+the remainder as body expressions, as if @var{indent} was supplied as
+the @code{lisp-indent-function} symbol property for @var{symbol} in
+Emacs.  Argument expressions are either printed on the same line as
+@var{symbol} or indented 4 columns beyond the base indentation of the
+enclosing list, and body expressions are indented 2 columns beyond the
+base indentation.
+
+@item @code{load-custom?} (default: @code{#t}) (type: boolean)
+Whether to load customizations created with the Emacs customization
+interface.  Because all configuration files created by this service are
+effectively read-only, the service modifies the default behavior of
+Emacs so that customizations are always saved in a separate
+@file{custom.el} file, which will be loaded when Emacs is initialized if
+this field is true.
+
+@item @code{early-init-file} (default: @code{()}) (type: list-of-elisp-gexp-or-file-likes)
+A list of Elisp expressions, G-expressions, or file-like objects to
+serialize to the Emacs early init file, the @file{early-init.el} file in
+the appropriate Emacs configuration directory.  For file-like objects
+that represent actual files in the store (for example, those returned by
+@code{local-file} or @code{plain-file}), the contents of each file are
+serialized.  For other file-like objects (for example, @code{package}
+objects), the output file name of the object in the store is serialized,
+as if it were referenced in an Elisp expression or G-expression.
+
+The Emacs home service automatically serializes some s-expressions to
+the early initialization file in order to ensure that the Emacs user
+directory is properly set according to the @code{user-emacs-directory}
+field.  Any expressions specified in this field are serialized before
+the automatically added expressions.  This comes with a trade-off: one
+the one hand, you can leverage this fact to insert header comments or a
+license preamble at the beginning of the file, where it would be
+expected, but on the other hand, the Emacs user initialization directory
+will not be set until after any s-expressions specified in this field
+have already been evaluated.
+
+@item @code{init-file} (default: @code{()}) (type: list-of-elisp-gexp-or-file-likes)
+A list additional of Elisp expressions, G-expressions, or file-like
+objects to serialize to the Emacs user initialization file, the
+@file{init.el} file in the appropriate Emacs configuration directory.
+For file-like objects that represent actual files in the store (for
+example, those returned by @code{local-file} or @code{plain-file}), the
+contents of each file are serialized.
+
+As with the @code{early-init-file} field, some expressions are
+automatically serialized to the Emacs user initialization file by the
+Emacs home service, particularly those to set up the @file{custom.el}
+file (see the @code{load-custom?} field).  Expressions specified using
+this field are serialized before any automatically added expressions.
+This means that you can specify header comments or license preambles
+that will occur at the beginning of the file, but that the
+@file{custom.el} file will be loaded after all expressions in this field
+are evaluated.
+
+@item @code{user-files} (default: @code{()}) (type: alist)
+An association list of filenames and file-like objects specifying files
+to create in the Emacs user directory.  For each entry, a file with the
+given filename will be created with the contents of the file-like
+object.  If a list of file-like objects is given for an entry, the new
+file will contain the combined text contents of all of the file-like
+objects in the list.  This field can be used to add configuration files
+for Emacs that should not be automatically loaded when Emacs is
+initialized.  Note that the Emacs user directory, which can be specified
+using the @code{user-emacs-directory} field, may not be the same as the
+directory containing the Emacs user initialization file.
+
+@item @code{servers} (default: @code{()}) (type: list-of-emacs-servers)
+A list of configurations for Emacs servers.
+
+@end table
+
+@end deftp
+
+@deftp {Data Type} emacs-server
+Available @code{emacs-server} fields are:
+
+@table @asis
+@item @code{name} (type: string)
+A string naming the server.  Users will subsequently be able to start
+the new server by using the command @code{herd start emacs-@var{name}}.
+To create Emacs client frames for the sever, users can use commands
+like: @code{emacsclient --create-frame --socket-name=@var{name}}.
+Because this string is meant for use in shell commands (and filenames),
+it should not contain any characters other than letters and digits and
+the characters @samp{-}, @samp{_}, and @samp{.}.
+
+@item @code{inherit-user-directory?} (default: @code{#t}) (type: boolean)
+Whether the server should share its Emacs user directory with that of
+the Emacs Home service.  When false, the server will use a subdirectory
+of the one used by the service for its own user directory.  When true
+(the default), the @code{user-emacs-directory} Emacs variable for the
+server will be set to that of the Emacs Home service, but the server
+will still load its own @file{early-init.el} and @file{init.el} files.
+See the @code{inherit-init-files?} field for how to inherit
+configuration from other Emacsen.
+
+@item @code{inherit-init-files?} (default: @code{#t}) (type: boolean)
+Whether to load the default configuration used by the Emacs Home
+service, that is, the initialization expressions specified by the
+@code{home-emacs-configuration} value for the Emacs Home service.  These
+are loaded in addition to any configuration specified for this specific
+server.
+
+Note that if this field is true and @code{inherit-user-directory?} is
+false, duplicate copies of of any files specified by the
+@code{user-files} field of the @code{home-emacs-configuration} value for
+the service are created in the Emacs user directory for the server.
+This ensures that any references to those files in the inherited
+configuration expressions will not fail in unexpected ways.
+
+@item @code{auto-start?} (default: @code{#t}) (type: boolean)
+Whether to start the server automatically.
+
+@item @code{debug?} (default: @code{#f}) (type: boolean)
+Whether to enable the Emacs Lisp debugger for errors in the
+initialization files of the server.
+
+@item @code{shepherd-requirements} (default: @code{()}) (type: list-of-symbols)
+A list of symbols specifying Shepherd services that must be started
+before the service for the Emacs server can be started (@pxref{Defining
+Services,,, shepherd,The GNU Shepherd Manual}).
+
+@item @code{environment-variables} (type: gexp)
+A G-expression specifying a list environment variables for the Emacs
+server.  The value of this field, if set, will override all default
+environment variables, and users should likely specify a G-expression
+whose expansion includes elements from
+@code{default-environment-variables} (@pxref{Service De- and
+Constructors,,, shepherd,The GNU Shepherd Manual}).
+
+@item @code{load-custom?} (default: @code{#t}) (type: boolean)
+Whether to load customizations created with the Emacs customization
+interface.  When @code{inherit-directory?} is true, customizations made
+within this specific server affect other Emacsen, and vice versa.
+Otherwise, the server has its own separate set of customizations.
+
+@item @code{early-init-file} (default: @code{()}) (type: list-of-elisp-gexp-or-file-likes)
+A list of Elisp expressions, G-expressions, or file-like objects to
+serialize to the Emacs early init file, the @file{early-init.el} file in
+the server's Emacs configuration directory.  For file-like objects that
+represent actual files in the store (for example, those returned by
+@code{local-file} or @code{plain-file}), the contents of each file are
+serialized.
+
+When the @code{inherit-init-files?} for the server is true,
+configuration specified here is serialized to the early init file after
+any early init file configuration expressions specified by the Emacs
+Home service type.
+
+@item @code{init-file} (default: @code{()}) (type: list-of-elisp-gexp-or-file-likes)
+A list Elisp expressions, G-expressions, or file-like objects to
+serialize to the Emacs user initialization file, the @file{init.el} file
+in the server's Emacs configuration directory.  For file-like objects
+that represent actual files in the store (for example, those returned by
+@code{local-file} or @code{plain-file}), the contents of each file are
+serialized.
+
+When the @code{inherit-init-files?} for the server is true,
+configuration specified here is serialized to the Emacs user
+initialization file after the any user initialization file configuration
+expressions specified by the Emacs Home service type.
+
+@item @code{user-files} (default: @code{()}) (type: alist)
+An association list of filenames and file-like objects specifying files
+to create in the server's Emacs user directory.  For each entry, a file
+with the given filename will be created with the contents of the
+file-like object.  If a list of file-like objects is given for an entry,
+the new file will contain the combined text contents of all of the
+file-like objects in the list.  This field can be used to add
+configuration files for Emacs that should not be automatically loaded
+when Emacs is initialized.  Note that the Emacs user directory for the
+server may not be the same as the directory containing its Emacs user
+initialization file.
+
+When the @code{inherit-user-directory?} field is false, user files are
+created in a subdirectory of the user directory used by the Emacs Home
+service, which will be populated with copies of all of files specified
+in the @code{user-files} field of the @code{home-emacs-configuration}
+value for the service.
+
+@end table
+
+@end deftp
+
+@deftp {Data Type} home-emacs-extension
+Available @code{home-emacs-extension} fields are:
+
+@table @asis
+@item @code{packages} (default: @code{()}) (type: list-of-file-likes)
+A list of additional Emacs-related packages to install.
+
+@item @code{indent-forms} (default: @code{()}) (type: alist)
+An association list of symbols and indentation rules.  Each entry is of
+the form (@var{symbol} . @var{indent}), where @var{symbol} is a symbol
+and @var{indent} is an integer specifying the number of argument
+expressions for @var{symbol}.
+
+@item @code{early-init-file} (default: @code{()}) (type: list-of-elisp-gexp-or-file-likes)
+A list of Elisp expressions, G-expressions, or file-like objects to
+serialize to the Emacs early init file, the @file{early-init.el} file in
+the Emacs configuration directory.
+
+@item @code{init-file} (default: @code{()}) (type: list-of-elisp-gexp-or-file-likes)
+A list Elisp expressions, G-expressions, or file-like objects to
+serialize to the Emacs user initialization file, the @file{init.el} file
+in the Emacs configuration directory.
+
+@item @code{user-files} (default: @code{()}) (type: alist)
+An association list of filenames and file-like objects specifying files
+to create in the Emacs user directory.  Note that the Emacs user
+directory may not be the same as the directory containing the Emacs user
+initialization file.
+
+@item @code{servers} (default: @code{()}) (type: list-of-emacs-servers)
+A list of configurations for Emacs servers.  You cannot specify multiple
+@code{emacs-server} objects with equivalent @code{name} fields.
+
+@end table
+
+@end deftp
+
+@cindex Emacs packages, Home service
+@cindex Emacs packages, configuration
+@defvar home-emacs-packages-service-type
+This service type extends the @code{home-emacs-service-type} and
+provides useful abstractions to configure Emacs packages using Scheme.
+Its value is a @code{home-emacs-packages-configuration} object.
+
+Emacs packages are configured by specifying @code{emacs-package}
+objects, which each encapsulate configuration relating to a specific
+Emacs package.
+
+Here is a sample Guix Home configuration that utilizes the
+@code{home-emacs-packages-service-type} to configure some Emacs
+packages:
+
+@lisp
+(use-modules (gnu home)
+             (gnu services)
+             (guix gexp)
+             (gnu home services emacs)
+             (gnu packages emacs-xyz))
+
+(home-environment
+  (services
+   (list
+    (service
+     home-emacs-packages-service-type
+     (home-emacs-packages-configuration
+      (packages
+       (list
+        (emacs-package
+          (name 'emacs)
+          (extra-init-files `(("my-init.el"
+                               . ,(local-file "my-init.el"))))
+          (keys-global '(("C-x q" . bury-buffer)
+                         ("C-x C-b" . ibuffer)))
+          (options '((confirm-kill-emacs . y-or-n-p)
+                     (visible-bell . #t)
+                     (initial-scratch-message . #f)))
+          (extra-init
+           (elisp*
+            (setq-default indent-tabs-mode nil)
+            (tool-bar-mode -1)
+            (delete-selection-mode 1))))
+        (emacs-package
+          (name 'orderless)
+          (package emacs-orderless)
+          (options
+           '((completion-styles . (orderless basic))
+             (completion-category-overrides
+              . ((file (styles basic partial-completion))))))))))))))
+@end lisp
+
+@noindent
+The configuration above will install Emacs, configure some basic
+settings, and install and configure the @code{emacs-orderless} package.
+
+The value for the @code{packages} field of the
+@code{home-emacs-packages-configuration} is a list containing two
+@code{emacs-package} objects, each encapsulating configuration relating
+to a specific package.
+
+The first @code{emacs-package} object configures the Emacs package named
+@code{emacs}, that is, Emacs itself.  This is a convenient way to
+configure general settings related to no particular package, while
+leveraging the abstractions provided by the Emacs packages Home service.
+
+The second @code{emacs-package} object configures the package
+@code{emacs-orderless}.  Notice that the @code{package} field of the
+@code{emacs-package} object specifies a Guix package, which by default
+causes it to be installed.  In this way, we can conveniently manage the
+installation and configuration of Emacs packages in one place.
+
+The configuration given here is roughly equivalent to the first
+configuration example given for the @code{home-emacs-service-type}
+above.
+
+The @code{home-emacs-packages-service-type} can be extended with more
+@code{emacs-package} objects.  Here is an example:
+
+@lisp
+(define %gnus-init-file
+        (elisp-file "gnus.el"
+                    (elisp*
+                     (setopt gnus-select-method '(nnnil "")
+                             gnus-secondary-select-methods '((nnml ""))
+                             mail-sources
+                             '((imap :server "mail.example.net"
+                                     :user "user@@example.net"
+                                     :port 993
+                                     :stream tls))))))
+
+(home-environment
+ (services
+  (list
+   ...
+   (simple-service
+    'emacs-mail-service
+    home-emacs-packages-service-type
+    (list
+     (emacs-package
+      (name 'gnus)
+      (user-files
+       `(("gnus.el" . ,%gnus-init-file)
+         ("signature" . ,(local-file "signature"))))
+      (options
+       `((gnus-init-file . ,(elisp (locate-user-emacs-file "gnus.el")))
+         (mail-user-agent . gnus-user-agent)
+         (read-mail-command . gnus))))
+     (emacs-package
+      (name 'smtpmail)
+      (options '((smtpmail-servers-requiring-authorization
+                  . "mail\\.example\\.net")
+                 (smtpmail-smtp-server . "mail.example.net")
+                 (smtpmail-smtp-service . 587)
+                 (smtpmail-smtp-user . "user@@example.net"))))
+     (emacs-package
+      (name 'message)
+      (options `((message-send-mail-function . smtpmail-send-it)
+                 (message-signature-file
+                  . ,(elisp (locate-user-emacs-file "signature")))))))))))
+@end lisp
+
+@noindent
+The configuration above extends the
+@code{home-emacs-packages-service-type} to configure the Emacs Gnus
+Newsreader (@pxref{Top,,,Gnus,The Gnus Newsreader}), the @code{smtpmail}
+package, and the @code{message} packages so that Emacs can send and
+receive mail.  This configuration is equivalent to the example given
+above for extending the @code{home-emacs-service-type}.
+
+Note that the @code{options} field of @code{emacs-package} is an
+association list of option names and values that can accept an Elisp
+expression as a value.  Normally, values that do not represent
+self-quoting data types, like symbols or lists, are quoted when they are
+serialized to Elisp to prevent them from being evaluated by Emacs.
+However, when an Elisp expression is specified, the value is not quoted
+and is instead evaluated at Emacs initialization time.
+
+By using an Elisp expression in this way, the Emacs variable
+@code{message-signature-file} is set to the result of evaluating the
+function @code{locate-user-emacs-file} at run time.  If the s-expression
+was simply specified without using an Elisp expression, the option would
+be set to the list resulting from quoting the s-expression.
+
+@end defvar
+
+The record types for configuring the Emacs packages Home service are
+described in detail below.
+
+@deftp {Data Type} home-emacs-packages-configuration
+Available @code{home-emacs-packages-configuration} fields are:
+
+@table @asis
+@item @code{package-serializer} (type: emacs-package-serializer)
+The serializer to use for configuration specified by
+@code{emacs-package} objects.
+
+@item @code{packages} (default: @code{()}) (type: list-of-emacs-packages)
+A list of @code{emacs-package} objects containing configuration for
+Emacs packages.
+
+@end table
+
+@end deftp
+
+@deftp {Data Type} emacs-package-serializer
+Available @code{emacs-package-serializer} fields are:
+
+@table @asis
+@item @code{name} (type: symbol)
+A symbol identifying the serializer.
+
+@item @code{procedure} (type: procedure)
+A procedure that takes one argument, an @code{emacs-package} object, and
+that should return a list of @code{elisp} objects or G-expressions
+containing configuration to serialize to the Emacs user initialization
+file.
+
+@item @code{dependencies} (default: @code{()}) (type: list-of-packages)
+An list of additional packages to install whenever this serializer is
+used.
+
+@item @code{indent-forms} (default: @code{()}) (type: alist)
+An association list of symbols and indentation rules.  Each entry is of
+the form (@var{symbol} .  @var{indent}), where @var{symbol} is a symbol
+and @var{indent} is an integer.  Values have the same effect as the
+@code{indent-forms} field in the @code{home-emacs-configuration} record.
+Note that indentation rules specified here will subsequently affect all
+Emacs Lisp expressions serialized by the Emacs home service, not just
+configuration for the Emacs packages Home service.
+
+@item @code{extra-init} (default: @code{()}) (type: list-of-elisp-or-gexps)
+A list additional of Elisp expressions or G-expressions to serialize to
+the Emacs user initialization file before all serialized Emacs package
+configuration.  This is useful for code that must set up or configure
+the package serializer itself.
+
+@item @code{description} (default: @code{""}) (type: string)
+A brief description of the serializer.
+
+@end table
+
+@end deftp
+
+@deftp {Data Type} emacs-package
+Available @code{emacs-package} fields are:
+
+@table @asis
+@item @code{name} (type: symbol)
+The symbol naming the Emacs package or library, as would be used with
+Emacs @code{require}.
+
+@item @code{package} (default: @code{()}) (type: package-or-null)
+A Guix package providing the Emacs package specified by @code{name}.  If
+the package is built into Emacs, or if there is no associated Guix
+package, this field should be set to the empty list (the default).
+
+@item @code{extra-packages} (default: @code{()}) (type: list-of-packages)
+A list of packages that provide additional functionality used by this
+package, but which are not installed automatically by the Guix package
+manager as propagated inputs of @code{package}.
+
+@item @code{install?} (default: @code{#t}) (type: boolean)
+Whether to install @code{package} and @code{extra-packages}.
+
+@item @code{extra-init-files} (default: @code{()}) (type: alist)
+An association list of filenames and file-like objects containing Emacs
+Lisp to load when Emacs is initialized.  For each entry, a file with the
+text contents of the file-like object, or the combined text contents of
+all of the file-like objects in a list if a list is specified, will be
+created with the given filename in the appropriate Emacs configuration
+directory (the directory where the @file{early-init.el} and
+@file{init.el} files are located).  These files will then be loaded when
+Emacs is initialized.
+
+This can be used as an escape hatch, similarly to the @code{extra-init}
+field.  It is useful when large amounts of code must be loaded in order
+to configure a package.
+
+Note that it is an error to specify files with the filenames
+@samp{init.el} and @samp{early-init.el}, because these files are already
+generated by the Emacs home service.
+
+Also note that it is up to the package serializer whether and when to
+load these files, although the @code{home-emacs-service-type} ensures
+that the files will be present in the relevant configuration
+directories.  @code{%emacs-simple-package-serializer} and
+@code{%emacs-use-package-serializer} both load the files at
+initialization time, before any expressions given in the
+@code{extra-init} field, regardless of the value of the
+@code{load-after-packages} field.
+
+@item @code{user-files} (default: @code{()}) (type: alist)
+An association list of filenames and file-like objects specifying files
+to create in the Emacs user directory.  For each entry, a file with the
+given filename will be created in the Emacs user directory with the
+contents of the file-like object.  If a list of file-like objects is
+given for an entry, the new file will contain the combined text contents
+of all of the file-like objects in the list.  This field should be used
+to add per-package files to the Emacs user directory.
+
+@item @code{load-force?} (default: @code{#f}) (type: boolean)
+Whether to force loading of this package immediately when Emacs is
+initialized, rather than deferring loading, for example, until an
+autoloaded function is invoked.  This is similar in effect to the
+keyword @code{:demand} from @code{use-package} and to the inverse of the
+keyword @code{:defer}.  The difference is that when this field is false,
+package loading should always be deferred; @code{use-package} normally
+does not defer loading when it does not set up autoloads, because it
+doesn't know that Guix handles autoloads on its own.
+
+@item @code{load-predicates} (default: @code{()}) (type: list-of-elisp-or-gexps)
+A list predicate expressions to evaluate when Emacs is initialized to
+determine whether to evaluate the configuration for this package.  When
+this list is not empty, @emph{all} other configuration for this package
+should be effectively surrounded in the Emacs user initialization file
+by a block of the form: @code{(when @var{load-predicates} @dots{})}.
+This is the supercharged Guix version of the @code{use-package}
+@code{:if} keyword!
+
+If multiple load predicates are specified, the behavior is determined by
+the package configuration serializer.  Both
+@code{%emacs-simple-package-serializer} and
+@code{%emacs-use-package-serializer} compose load predicates using
+@code{and}, so that all load predicates in the list must be satisfied in
+order for the package configuration to be evaluated.
+
+@item @code{load-after-packages} (default: @code{()}) (type: list-of-symbols)
+A list of symbols for Emacs packages that must be loaded before this
+package is loaded.  Only after all of the packages in the list have been
+loaded by Emacs should configuration for this package be evaluated.  This
+is similar to a simplified version of the @code{:after} keyword from
+@code{use-package}.
+
+@item @code{load-paths} (default: @code{()}) (type: list-of-string-or-file-likes)
+A list of additional load paths to add to the Emacs @code{load-paths}
+variable.  Load paths can be specified either as strings or as file-like
+objects, in which case a path to the respective store item is
+substituted.
+
+@item @code{autoloads} (default: @code{()}) (type: list-of-symbols)
+A list of Emacs functions from the package to autoload.  This can be
+useful, for example, when defining custom commands in the Emacs user
+initialization file that use functions which are not autoloaded by
+default.
+
+@item @code{autoloads-interactive} (default: @code{()}) (type: list-of-symbols)
+A list of additional Emacs interactive commands from the package to
+autoload, so that they can be invoked interactively before the package
+is loaded.
+
+@item @code{keys-global} (default: @code{()}) (type: alist)
+An association list of key sequences (as strings or vectors) and Emacs
+commands to bind in the global keymap.
+
+@item @code{keys-global-keymaps} (default: @code{()}) (type: alist)
+An association list of key sequences and Emacs keymap variables to bind
+to them in the global keymap.  The keymap variables should be symbols
+that define keymaps in the package; they can be effectively autoloaded
+using this assumption.
+
+@item @code{keys-override} (default: @code{()}) (type: alist)
+An association list of key sequences and symbols naming Emacs commands
+to bind in the global override map.  These key bindings have a higher
+precedence than local and global keybindings.
+
+@item @code{keys-local} (default: @code{()}) (type: list-of-emacs-keymaps)
+A list of key binding configurations for specific keymaps, each
+contained in an @code{emacs-keymap} object.
+
+@item @code{options} (default: @code{()}) (type: alist)
+An association list of user options and values for this package.  Options
+should be symbols naming Emacs variables, and values can be any object
+that can be serialized to Elisp.  For values, primitive Scheme data
+types are implicitly quoted, including lists and symbols.  To instead
+set an option to the value of an expression to be evaluated at Emacs
+initialization time, either use an Elisp expression (e.g., specified
+with the @code{elisp} form) or a G-expression for a value.
+
+@item @code{faces} (default: @code{()}) (type: alist)
+An association list of face symbols and face specs.  @xref{Defining
+Faces,,,elisp,The Emacs Lisp Manual} for the format of face specs.
+
+@item @code{hooks} (default: @code{()}) (type: alist)
+An association list of hooks and functions to add to them.  Each entry
+is a pair of symbols.  Hook symbols in Emacs should end in @samp{-hook},
+but the @code{%emacs-simple-package-serializer} and
+@code{%emacs-use-package-serializer} serializers effectively add this
+suffix when necessary.
+
+@item @code{auto-modes} (default: @code{()}) (type: alist)
+An association list of filename patterns as regular expression strings
+and Emacs mode functions to call when visiting files with filenames that
+match the patterns.  @xref{Auto Major Mode,,,elisp,The Emacs Lisp
+Manual} for details.
+
+@item @code{magic-modes} (default: @code{()}) (type: alist)
+An association list regular expression strings and Emacs mode functions
+to call when visiting files that begin with matching text.  @xref{Auto
+Major Mode,,,elisp,The Emacs Lisp Manual} for details.
+
+@item @code{extra-after-load} (default: @code{()}) (type: list-of-elisp-or-gexps)
+A list of Elisp expressions or G-expressions to evaluate after the
+package is loaded, as with the Emacs @code{eval-after-load} function.
+Elisp expressions can be specified using the @code{elisp} and
+@code{elisp*} forms.
+
+@item @code{extra-init} (default: @code{()}) (type: list-of-elisp-or-gexps)
+A list of Elisp expressions or G-expressions to evaluate immediately
+when Emacs is initialized, even if loading is deferred due to the
+@code{load-force?} field.  Note that the @code{load-predicates} field
+should still determine whether these expressions are evaluated, and they
+will only be evaluated after all packages specified in the
+@code{load-after-packages} field have been loaded.
+
+@item @code{extra-keywords} (default: @code{()}) (type: alist)
+An association list of keys and lists of extra Elisp expressions or
+G-expressions.  Keys can potentially be any keyword or symbol object;
+keywords are automatically serialized to their Emacs Lisp equivalent
+(e.g., @code{#:keyword} is serialized as @code{:keyword}).  The meanings
+of entries is specific to each package serializer, and any key may be
+ignored by a package serializer.  This field is currently ignored by the
+@code{%emacs-simple-package-serializer}.  Entries in this list matching
+@code{use-package} keywords will be spliced by the
+@code{%emacs-use-package-serializer} into the @code{use-package} body,
+after all other forms.
+
+@end table
+
+@end deftp
+
+@deftp {Data Type} emacs-keymap
+Available @code{emacs-keymap} fields are:
+
+@table @asis
+@item @code{name} (default: @code{global-map}) (type: symbol)
+The symbol of the Emacs keymap in which to bind keys.
+
+@item @code{package-name} (default: @code{#f}) (type: symbol-or-false)
+The symbol naming the Emacs package providing the keymap, as would be
+used with Emacs @code{require}.  If this field is false (the default),
+then the package for which the keymap is being configured should define
+the keymap or the keymap should otherwise be defined by the time the
+configuration for the package is evaluated.  Note that when the
+@code{prefix?} field is true, this implies that the keymap will be
+defined and this field has no effect.
+
+@item @code{prefix?} (default: @code{#f}) (type: boolean)
+Whether to create a new keymap and assign it to a prefix command named
+@code{name}.  Specifically, the new keymap is assigned to the function
+definition of the symbol @code{name}.  This is useful when the keymap
+does not already exist, but the user would like to bind it to a prefix
+key using the @code{keys-global} field in an @code{emacs-package}
+configuration object or even using the @code{keys} field in another
+@code{emacs-keymap} object.  Because a prefix command is a valid
+function, no special handling is needed to use it in keybinding
+definitions where commands are expected.  Another benefit of prefix
+commands comes when using keybinding introspection features such as
+@code{which-key-mode}: @code{name} will be displayed as a prefix key
+description when it is bound to a prefix command declared in this way,
+whereas ad-hoc prefix key bindings like @kbd{C-c d d} where @kbd{C-c d}
+is not explicitly defined will not have explicitly meaningful
+descriptions.
+
+Note that when this field true, the @code{package-name} field has no
+effect.
+
+@item @code{repeat?} (default: @code{#f}) (type: boolean)
+Whether to make this keymap a repeat map (@pxref{Repeating,,, emacs,The
+GNU Emacs Manual}).  Repeat maps are created by setting the
+@code{repeat-map} symbol property for each key definition in @code{keys}
+to the @code{name} of this keymap.  Use the @code{repeat-exit} field to
+override this setting for specific bindings.
+
+@item @code{repeat-exit} (default: @code{()}) (type: list-of-symbols)
+A list of commands that exit the repeat map.  When @code{repeat?} is
+true, these commands do not get the @code{repeat-map} property.  The
+meaning of this field is similar to that of the @code{:exit} keyword
+used by the @code{defvar-keymap} function in Emacs.  This field has no
+effect when @code{repeat?} is false.
+
+@item @code{repeat-enter} (default: @code{()}) (type: list-of-symbols)
+A list of additional commands that enter the repeat map.  When
+@code{repeat?} is true, these commands get the @code{repeat-map}
+property, even when they are not bound in the keymap.  This is only
+useful when a command is not bound in @code{name}, but the repeat map
+should be accessible after that command is invoked (e.g., with
+@kbd{M-x}).  The meaning of this field is similar to that of the
+@code{:enter} keyword used by the @code{defvar-keymap} function in
+Emacs.  This field has no effect when @code{repeat?} is false.
+
+@item @code{disabled-commands} (default: @code{()}) (type: alist)
+An association list of command symbols and whether to disable them.  When
+a disabled command is interactively invoked, Emacs asks for confirmation
+from the user (@pxref{Disabling,,, emacs,The GNU Emacs Manual}).  The
+values of this alist should be booleans, which will be stored as the
+value of the @code{disabled} property of each respective command symbol.
+Thus, to disable the @code{transpose-chars} command and enable the
+@code{erase-buffer} command, you can use:
+
+@lisp
+'((transpose-chars .  #t)
+  (erase-buffer .  #f))
+@end lisp
+
+@item @code{keys} (default: @code{()}) (type: alist)
+An association list of key sequences and binding definitions.  Key
+sequences are Emacs-specific string or vector representations of
+sequences of keystrokes or events.  Strings should be valid arguments to
+the Emacs function @code{kbd}, and they are preferred over the low-level
+vector representations (@pxref{Keymaps,,, elisp,The Emacs Lisp Manual}).
+Binding definitions should be Emacs command symbols.  As a special case,
+when a binding definition is the boolean false, the key is unset in the
+keymap.
+
+@end table
+
+@end deftp
+
+
+@cindex Emacs package serializers, for Emacs packages Home service
+As we have seen, we can customize how configuration for Emacs packages
+is serialized to the Emacs user initialization file by using the
+@code{package-serializer} field of the
+@code{home-emacs-packages-configuration} record type.
+
+There are two predefined package serializers:
+@code{%emacs-simple-package-serializer} and
+@code{%emacs-use-package-serializer}.
+
+@defvar %emacs-simple-package-serializer
+An Emacs package configuration serializer that configures Emacs using
+minimal, built-in Emacs mechanisms, instead of complex macros such as
+@code{use-package}.
+@end defvar
+
+@defvar %emacs-use-package-serializer
+An Emacs package configuration serializer that configures Emacs with the
+@code{use-package} macro.
+@end defvar
+
+@cindex Emacs package serializers, defining for Emacs packages Home service
+We can also create custom package serializers by defining our own
+@code{emacs-package-serializer} objects.  The two mandatory fields are
+the @code{name} and the @code{procedure} fields.  @code{name} is an
+arbitrary symbol (currently unused), and @code{procedure} is a procedure
+that takes one argument, an @code{emacs-package} object to be
+serialized, and should return a list of Elisp expressions or
+G-expressions to serialize to the Emacs initialization file.
+
+When defining a new package serializer, it is advisable to refer to the
+documentation for the @code{emacs-package} record type, which lays out
+some implementation guidelines that package serializers should follow
+for each field.  It is up to you to implement your serializer in a way
+that is consistent with those guidelines, or not.
+
+Here is an example of a hypothetical
+@code{%emacs-null-package-serializer} that only serializes a simple
+comment naming each package:
+
+@lisp
+(define (emacs-package->null-elisp config)
+  "Return from 'emacs-package' CONFIG a list of Elisp expressions that
+configures Emacs by serializing only comments."
+  (match-record config <emacs-package>
+                (name)
+    (let ((comment-string (string-append ";;; "
+                                         (symbol->string name)
+                                         "\n")))
+      (list (elisp (unelisp-comment comment-string))))))
+
+(define %emacs-null-package-serializer
+  (emacs-package-serializer
+   (name 'emacs-null-package)
+   (procedure emacs-package->null-elisp)
+   (description "An Emacs package serializer that doesn't do anything.")))
+@end lisp
+
+@cindex importing configuration, for Emacs Home services
+You may have existing Emacs initialization files, but translating them
+into Scheme configuration records can be tedious.  The @command{guix
+home import} command can be used to generate Emacs Home service
+configurations from existing Emacs configuration files (@pxref{Declaring
+the Home Environment}).  Additionally, the @code{(gnu home services
+emacs)} module provides two utility functions to aid in this process:
+@code{input->home-emacs-packages-configuration} and
+@code{input->home-emacs-packages-configuration-sexp}.
+
+The following example returns an s-expression that declares a
+@code{home-emacs-packages-configuration} record corresponding to the
+given Emacs initialization file:
+
+@lisp
+(call-with-input-file "/home/user/.config/emacs/init.el"
+  (lambda (port)
+    (input->home-emacs-packages-configuration-sexp port)))
+@end lisp
+
+@deffn {Procedure} input->home-emacs-packages-configuration port
+Return a @code{home-emacs-packages-configuration} record from Elisp code
+read from @var{port}.
+@end deffn
+
+@deffn {Procedure} input->home-emacs-packages-configuration-sexp port
+Return a s-expression in Scheme that declares a
+@code{home-emacs-packages-configuration} record from Elisp code read
+from @var{port}.
+@end deffn
 
 @node Guix Home Services
 @subsection Guix Home Services
diff --git a/gnu/home/services/emacs.scm b/gnu/home/services/emacs.scm
new file mode 100644
index 0000000000..585a4b4d6f
--- /dev/null
+++ b/gnu/home/services/emacs.scm
@@ -0,0 +1,3101 @@
+;;; GNU Guix --- Functional package management for GNU
+
+;;; Copyright © 2023 ( <paren@disroot.org>
+;;; Copyright © 2023 David Wilson <david@daviwil.com>
+;;; Copyright © 2023-2025 Kierin Bell <fernseed@fernseed.me>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu home services emacs)
+  #:use-module (gnu home services)
+  #:use-module (gnu home services shepherd)
+  #:autoload   (gnu packages emacs) (emacs emacs-minimal)
+  #:autoload   (gnu packages emacs-xyz) (emacs-use-package)
+  #:use-module (gnu services configuration)
+  #:use-module (guix gexp)
+  #:use-module (guix records)
+  #:use-module (guix packages)
+  #:use-module (guix utils)
+  #:use-module (guix modules)
+  #:use-module (guix read-print)
+  #:use-module (guix formatters elisp)
+  #:use-module (guix store)
+  #:use-module (guix monads)
+  #:use-module (guix i18n)
+  #:use-module ((guix diagnostics)
+                #:select (formatted-message))
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-35)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 receive)
+  #:use-module (ice-9 binary-ports)
+  #:use-module (rnrs bytevectors)
+  #:use-module (ice-9 control)
+  #:use-module (ice-9 regex)
+  #:use-module (language elisp parser)
+  #:export (elisp?
+            elisp->sexp
+            sexp->elisp
+            elisp
+            elisp*
+
+            elisp-file
+            elisp-file*
+            composite-elisp-file
+
+            emacs-server
+            emacs-server?
+            emacs-server-name
+            emacs-server-inherit-user-directory?
+            emacs-server-inherit-init-files?
+            emacs-server-auto-start?
+            emacs-server-debug?
+            emacs-server-shepherd-requirements
+            emacs-server-environment-variables
+            emacs-server-load-custom?
+            emacs-server-early-init-file
+            emacs-server-init-file
+            emacs-server-user-files
+
+            home-emacs-configuration
+            home-emacs-configuration?
+            home-emacs-configuration-emacs
+            home-emacs-configuration-packages
+            home-emacs-configuration-user-emacs-directory
+            home-emacs-configuration-native-compile?
+            home-emacs-configuration-indent-forms
+            home-emacs-configuration-load-custom?
+            home-emacs-configuration-early-init-file
+            home-emacs-configuration-init-file
+            home-emacs-configuration-user-files
+            home-emacs-configuration-servers
+
+            home-emacs-extension
+            home-emacs-extension?
+            home-emacs-extension-packages
+            home-emacs-extension-indent-forms
+            home-emacs-extension-early-init-file
+            home-emacs-extension-init-file
+            home-emacs-extension-user-files
+            home-emacs-extension-servers
+
+            emacs-server->provision
+
+            home-emacs-service-type
+
+            emacs-keymap
+            emacs-keymap?
+            emacs-keymap-name
+            emacs-keymap-package-name
+            emacs-keymap-prefix?
+            emacs-keymap-repeat?
+            emacs-keymap-repeat-exit
+            emacs-keymap-repeat-enter
+            emacs-keymap-keys
+
+            emacs-package
+            emacs-package?
+            emacs-package-name
+            emacs-package-package
+            emacs-package-extra-packages
+            emacs-package-install?
+            emacs-package-extra-init-files
+            emacs-package-user-files
+            emacs-package-load-force?
+            emacs-package-load-predicates
+            emacs-package-load-after-packages
+            emacs-package-load-paths
+            emacs-package-autoloads
+            emacs-package-autoloads-interactive
+            emacs-package-keys-global
+            emacs-package-keys-global-keymaps
+            emacs-package-keys-override
+            emacs-package-keys-local
+            emacs-package-options
+            emacs-package-faces
+            emacs-package-hooks
+            emacs-package-auto-modes
+            emacs-package-magic-modes
+            emacs-package-extra-after-load
+            emacs-package-extra-init
+            emacs-package-extra-keywords
+
+            emacs-package-serializer
+            emacs-package-serializer?
+            emacs-package-serializer-name
+            emacs-package-serializer-procedure
+            emacs-package-serializer-dependencies
+            emacs-package-serializer-indent-forms
+            emacs-package-serializer-extra-init
+            emacs-package-serializer-description
+
+            %emacs-extra-init-files-path
+            %emacs-extra-init-files-path-variable
+            emacs-package-extra-init-file->sexp
+
+            emacs-package->simple-elisp
+            %emacs-simple-package-serializer
+            emacs-package->use-package-elisp
+            %emacs-use-package-serializer
+
+            home-emacs-packages-configuration
+            home-emacs-packages-configuration?
+            home-emacs-packages-configuration-package-serializer
+            home-emacs-packages-configuration-packages
+
+            emacs-packages->elisp
+
+            home-emacs-packages-service-type
+
+            input->home-emacs-packages-configuration
+            input->home-emacs-packages-configuration-sexp))
+
+;;; Commentary:
+;;;
+;;; Services for the GNU Emacs extensible text editor.
+;;;
+;;; Code:
+
+
+;;;
+;;; Elisp expression objects.
+;;;
+
+(define-record-type* <elisp> %elisp
+  make-elisp
+  elisp?
+  this-elisp
+  (sexp elisp-sexp))
+
+(define (dotted-list?* obj)
+  (and (pair? obj)
+       (dotted-list? obj)))
+
+(define list->dotted-list
+  (match-lambda
+    ((? list? lst)
+     (match (last-pair lst)
+       (((? pair?) . ())
+        ;; Prevent, e.g., '((quote a) (quote b)) -> '((quote a) quote b).
+        lst)
+       (_
+        (apply cons* lst))))
+    (x
+     x)))
+
+(define (fold-right/elisp fhere fup fcons seed exp)
+  "Recurse into subexpressions and 'elisp' objects in EXP, applying FHERE, FUP
+and FCONS.  FHERE transforms atoms, FUP transforms accumulators after
+traversing lists, and FCONS joins atoms or lists with accumulators while
+traversing lists.  FHERE and FCONS each take two arguments: an element and an
+accumulator.  For FUP, the second argument is the accumulator, and the first
+argument is either the empty list or false if its second argument was derived
+from a dotted list.  The accumulator starts as SEED."
+  (define (reverse* lst)
+    (let loop ((lst lst)
+               (acc '()))
+      (match lst
+        ((? null?)
+         acc)
+        ((not (? pair?))
+         ;; Convert dotted lists into proper lists.
+         (cons lst acc))
+        ((head . tail)
+         (loop tail (cons head acc))))))
+
+  (match exp
+    ((? elisp?)
+     (fold-right/elisp fhere fup fcons seed (elisp-sexp exp)))
+    ((or (not (? pair?))
+         (? null?))
+     ;; The empty list should be passed to FHERE along with atoms.
+     (fhere exp seed))
+    (_
+     (let loop ((exp (reverse* exp))
+                (acc seed)
+                (dotted? (dotted-list?* exp)))
+       (match exp
+         ((? null?)
+          ;; XXX: FUP must handle any transformation of ACC back into a dotted
+          ;; list, since FHERE could have transformed the last element of ACC
+          ;; into a list, in which case we can't just use 'list->dotted-list'
+          ;; to get a dotted list back.
+          (fup (if dotted? #f exp) acc))
+         ((head . tail)
+          (loop tail
+                (fcons (fold-right/elisp fhere fup fcons seed head)
+                       acc)
+                dotted?)))))))
+
+(define (elisp->sexp exp)
+  "Return an s-expression containing the contents of Elisp expression EXP."
+  (fold-right/elisp (lambda (t s) t)
+                    (lambda (t s)
+                      (if (not t) (list->dotted-list s) s))
+                    cons
+                    '()
+                    exp))
+
+(define (elisp->lowerable-sexp exp)
+  "Return an s-expression from EXP that is lowerable within a G-expression,
+that is, strip '<vertical-space>', '<page-break>', and '<comment>' objects."
+  (let ((result (fold-right/elisp (lambda (t s) t)
+                                  (lambda (t s)
+                                    (if (not t) (list->dotted-list s) s))
+                                  (lambda (t s) (if (blank? t) s (cons t s)))
+                                  '()
+                                  exp)))
+    ;; XXX: What to do when an Elisp expression *is* a <blank>?
+    (if (blank? result) '() result)))
+
+(define-gexp-compiler (elisp-compiler (elisp <elisp>) system target)
+  ;; "Compile" an 'elisp' object by stripping '<vertical-space>',
+  ;; '<page-break>', and '<comment>' objects, so that it can be 'ungexp'd
+  ;; within a G-expression.
+  (with-monad %store-monad
+    (return (elisp->lowerable-sexp elisp))))
+
+(define (sexp->elisp sexp)
+  "Return an Elisp expression object containing SEXP."
+  (%elisp (sexp sexp)))
+
+(define-syntax elisp
+  ;; Create an '<elisp>' object from EXP, including any substitutions made
+  ;; with 'unelisp', 'unelisp-splicing', 'unelisp-comment', 'unelisp-newline',
+  ;; or 'unelisp-page-break'.  Modified from the 'gexp' macro in (guix gexp).
+  (lambda (s)
+    (define (substitute-unelisp e)
+      (syntax-case e (unelisp
+                      unelisp-splicing
+                      unelisp-comment
+                      unelisp-newline
+                      unelisp-page-break)
+        ((unelisp exp)
+         #'exp)
+        (((unelisp-splicing exp) rest ...)
+         #`(append exp #,(substitute-unelisp #'(rest ...))))
+        ((unelisp-comment str)
+         #'(comment str))
+        ((unelisp-newline)
+         #'(vertical-space 0))
+        ((unelisp-page-break)
+         #'(page-break))
+        ((exp0 . exp)
+         #`(cons #,(substitute-unelisp #'exp0)
+                 #,(substitute-unelisp #'exp)))
+        (x #''x)))
+
+    (syntax-case s ()
+      ((_ exp)
+       (let ((sexp* (substitute-unelisp #'exp)))
+         #`(%elisp (sexp #,sexp*)))))))
+
+(define-syntax elisp*
+  ;; Return a list of <elisp> objects which contains each EXP.
+  (lambda (s)
+    (syntax-case s ()
+      ((_ exp ...)
+       (with-syntax (((elts ...)
+                      (map
+                       (lambda (e)
+                         #`(elisp #,e))
+                       #'(exp ...))))
+         #'(list elts ...))))))
+
+
+;;;
+;;; Elisp files.
+;;;
+
+(define %printer-module-closure (source-module-closure
+                                 '((guix formatters elisp)
+                                   (guix read-print))))
+
+(define (constant? obj)
+  "Return whether OBJ is self-quoting."
+  (or (boolean? obj)
+      (char? obj)
+      (string? obj)
+      (keyword? obj)
+      (number? obj)
+      (array? obj)))
+
+(define* (elisp->printer exp #:key (special-forms '()))
+  "Return a G-expression containing a lambda that prints the Elisp
+expression (<elisp> objects or s-expressions) or G-expression EXP to the port
+specified in its argument.  See 'elisp-file' for a description of
+SPECIAL-FORMS."
+  (define (object->pp-quoted exp)
+    (match exp
+      ((? vertical-space?)
+       `((@ (guix read-print) vertical-space)
+         ,(vertical-space-height exp)))
+      ((? page-break?)
+       '((@ (guix read-print) page-break)))
+      ((? comment?)
+       `((@ (guix read-print) comment)
+         ,(comment->string exp)))
+      ((? constant?)
+       exp)
+      (_
+       (list 'quote exp))))
+
+  (define (elisp->pp-arg exp)
+    ;; Doing some of this on the derivation side with a macro similar to
+    ;; 'quasiquote' might be cleaner, at the expense of an extra tree
+    ;; traversal.
+    (fold-right/elisp (lambda (t s)
+                        (object->pp-quoted t))
+                      (lambda (t s)
+                        (if (not t)
+                            ;; Transform S back into a dotted list, but only
+                            ;; after Scheme 'quote' forms have been evaluated.
+                            ;; Also, never allow <comment>, <vertical-space>,
+                            ;; or <page-break> record constructors to
+                            ;; terminate a dotted list ('pretty-print-elisp'
+                            ;; shouldn't print them anyway).
+                            (match (last-pair s)
+                              ((`((@ (guix read-print) ,(or 'vertical-space
+                                                            'page-break
+                                                            'comment)) . ,_)
+                                . ())
+                               `(list ,@s))
+                              (_ `(apply cons* (list ,@s))))
+                            `(list ,@s)))
+                      cons
+                      '()
+                      exp))
+
+  (with-imported-modules %printer-module-closure
+    #~(lambda (port)
+        #$@(match exp
+             ((or (? gexp? exp)
+                  (and (? file-like?) (not (? elisp?)) exp))
+              ;; 'ungexp' G-expressions or anything that has a G-expression
+              ;; compiler other than Elisp objects.
+              (list
+               #~((@ (guix formatters elisp) pretty-print-elisp)
+                  port
+                  (quote #$exp)
+                  #:special-forms '#$special-forms)
+               #~(display "\n" port)))
+             (exp
+              ;; S-expressions can be treated like 'elisp' objects, and we can
+              ;; use s-exps internally to avoid the overhead of converting
+              ;; 'elisp' objects back into s-exps.
+              (append
+               (list
+                #~((@ (guix formatters elisp) pretty-print-elisp)
+                   port
+                   #$(elisp->pp-arg exp)
+                   #:format-comment
+                   (@ (guix read-print) canonicalize-comment)
+                   #:special-forms '#$special-forms))
+               (if (comment? exp)
+                   '()
+                   (list
+                    #~(display "\n" port)))))))))
+
+(define* (elisp->file-builder exps #:key (special-forms '()))
+  "Return a G-expression that builds a file containing the Elisp
+expressions (<elisp> objects or s-expressions) or G-expressions in list EXPS.
+See 'elisp-file' for a description of SPECIAL-FORMS."
+  #~(begin
+      (call-with-output-file #$output:out
+        (lambda (port)
+          ;; XXX: Set the encoding to allow strings to be displayed without
+          ;; Unicode characters being mangled in store items.
+          (set-port-encoding! port "UTF-8")
+          #$@(map
+              (lambda (exp)
+                #~(apply
+                   #$(elisp->printer exp #:special-forms special-forms)
+                   (list port)))
+              exps)))))
+
+(define* (elisp-file name exps #:key (special-forms '()))
+  "Return an object representing the store file NAME, an Emacs Lisp file that
+contains EXPS, a list of Elisp expression objects or G-expressions.
+
+Custom indentation rules can be specified with SPECIAL-FORMS, an association
+list where each entry is of the form (SYMBOL . INDENT).  When SYMBOL occurs at
+the beginning of a list in an expression in EXPS, the first INDENT expressions
+after SYMBOL are indented as arguments and the remainder are indented as body
+expressions, as if INDENT was the value of the 'lisp-indent-function' symbol
+property for SYMBOL in Emacs.  As in Emacs, argument expressions, if they
+cannot be pretty-printed on the same line as SYMBOL, are indented 4 columns
+beyond the base indentation of the enclosing list, and body expressions are
+indented 2 columns beyond the base indentation.
+
+This is the declarative counterpart of 'elisp-file*'."
+  (define builder
+    (elisp->file-builder exps #:special-forms special-forms))
+
+  (computed-file name
+                 builder
+                 #:local-build? #t
+                 #:options '(#:substitutable? #f)))
+
+(define* (elisp-file* name exps #:key (special-forms '()))
+  "Return as a monadic value a derivation that builds an Elisp file named NAME
+containing the expressions in EXPS, a list of Elisp expression objects or
+G-expressions.
+
+This is the monadic counterpart of 'elisp-file', which see for a description
+of SPECIAL-FORMS."
+  (define builder
+    (elisp->file-builder exps #:special-forms special-forms))
+
+  (gexp->derivation name builder
+                    #:local-build? #t
+                    #:substitutable? #f))
+
+(define* (elisp-or-file-like->file-builder exps #:key (special-forms '()))
+  "Return a G-expression that builds a file containing the Elisp
+expressions (<elisp> objects or s-expressions), G-expressions, or file-likes
+in list EXPS.  The contents of file-like objects are spliced into the output
+file.  See 'elisp-file' for a description of SPECIAL-FORMS."
+  (with-imported-modules (source-module-closure
+                          '((ice-9 binary-ports)
+                            (rnrs bytevectors)))
+    #~(begin
+        (call-with-output-file #$output:out
+          (lambda (port)
+            ;; XXX: Set the encoding to allow strings to be displayed without
+            ;; Unicode characters being mangled in store items (for
+            ;; 'elisp->printer').
+            (set-port-encoding! port "UTF-8")
+            #$@(map
+                (match-lambda
+                  ;; XXX: Only try to splice contents of file-likes that are
+                  ;; actually files.
+                  ((or (? local-file? exp)
+                       (? plain-file? exp)
+                       (? computed-file? exp)
+                       (? program-file? exp))
+                   ;; XXX: Binary I/O ensures that Unicode is not mangled.
+                   ;; 'get-bytevector-all' returns <#eof> when port is empty.
+                   #~(let ((bv (call-with-input-file #$exp
+                                 (@ (ice-9 binary-ports)
+                                    get-bytevector-all))))
+                       ((@ (ice-9 binary-ports) put-bytevector) port
+                        (if (eof-object? bv)
+                            ((@ (rnrs bytevectors) make-bytevector) 0)
+                            bv))))
+                  (exp
+                   #~(apply
+                      #$(elisp->printer exp #:special-forms special-forms)
+                      (list port))))
+                exps))))))
+
+(define* (composite-elisp-file name exps #:key (special-forms '()))
+  "Return an object representing the store file NAME, an Emacs Lisp file that
+contains EXPS, a list of Elisp expression objects, G-expressions, or file-like
+objects.  The contents of file-like objects are spliced into the output file.
+See 'elisp-file' for a description of SPECIAL-FORMS."
+  (define builder
+    (elisp-or-file-like->file-builder exps #:special-forms special-forms))
+
+  (computed-file name
+                 builder
+                 #:local-build? #t
+                 #:options '(#:substitutable? #f)))
+
+
+;;;
+;;; Helper functions
+;;;
+
+(define-syntax alist-sanitizer
+  ;; Construct a lambda expression that matches each KEY-PAT and VALUE-PAT
+  ;; pair against each entry of its argument, an alist.  If no pair matches,
+  ;; or if its argument is not an alist, the lambda signals an error
+  ;; displaying FIELD-NAME and the value of its argument.  Otherwise, the
+  ;; return value of the lambda is its argument.
+  (lambda (s)
+    (syntax-case s ()
+      ((_ field-name (key-pat . value-pat) ...)
+       (with-syntax (((clauses ...)
+                      (map
+                       (lambda (spec)
+                         (syntax-case spec ()
+                           ((key-pat . value-pat)
+                            ;; Note that entries of the form (A B) are
+                            ;; equivalent to (A . (B))---i.e., the tail is
+                            ;; really a list, not an atom.  However, (A B)
+                            ;; where B is an Elisp expression is converted
+                            ;; into (A . B).
+                            #'(((and key-pat key)
+                                . (and value-pat value))
+                               (cons key value)))))
+                       #'((key-pat . value-pat) ...))))
+         #'(lambda (val)
+             (map
+              (lambda (expr)
+                (match expr
+                  clauses ...
+                  (_ (configuration-field-error #f 'field-name val))))
+              val))))
+      ((_ field-name '(key-pat . value-pat) ...)
+       #'(alist-sanitizer field-name (key-pat . value-pat) ...))
+      ((_ field-name)
+       #'(alist-sanitizer field-name (_ . _))))))
+
+(define (ensure-list obj)
+  "Return OBJ as a list."
+  (if (list? obj) obj (list obj)))
+
+(define (merge-alists lst1 . rest)
+  "Combine association list LST1 and the association lists in REST, merging
+the values for any duplicate keys into a single list value.  Key comparison is
+done with 'equal?'."
+  (fold-right (lambda (elem ret)
+                (let ((entry (assoc (car elem) ret)))
+                  (if entry
+                      (acons (car elem)
+                             (append (ensure-list (cdr elem))
+                                     (ensure-list (cdr entry)))
+                             (alist-delete! (car elem) ret))
+                      (cons elem ret))))
+              '()
+              (apply append lst1 rest)))
+
+(define (file-name-concat dir . rest)
+  "Concatenate DIR and REST filename components.  Any final slashes are
+stripped from the resulting filename."
+  (string-join (append (list (string-trim-right dir #\/))
+                       (map (cut string-trim-both <> #\/)
+                            rest))
+               "/"))
+
+(define emacs-config-filename
+  (cut file-name-concat "emacs" <>))
+
+(define (composite-file name . files)
+  "Return an object representing store file NAME containing the text contents
+of all file-like objects in FILES."
+  (define builder
+    (with-imported-modules (source-module-closure
+                            '((ice-9 binary-ports)
+                              (rnrs bytevectors)))
+      #~(begin
+          (call-with-output-file #$output:out
+            (lambda (port)
+              (set-port-encoding! port "UTF-8")
+              #$@(interpose
+                  (map
+                   (lambda (file)
+                     ;; XXX: 'get-bytevector-all' returns <#eof> when the port
+                     ;; is empty.
+                     #~(let ((bv (call-with-input-file #$file
+                                   (@ (ice-9 binary-ports)
+                                      get-bytevector-all))))
+                         ((@ (ice-9 binary-ports) put-bytevector) port
+                          (if (eof-object? bv)
+                              ((@ (rnrs bytevectors) make-bytevector) 0)
+                              bv))))
+                   files)
+                  #~(display "\n")
+                  'suffix))))))
+
+  (computed-file name builder))
+
+
+;;;
+;;; Emacs home service
+;;;
+
+(define %default-emacs emacs)
+(define %default-emacs-config-dir "~/.config/emacs/")
+(define %emacs-user-init-filename "init.el")
+(define %emacs-early-init-filename "early-init.el")
+
+(define list-of-file-likes?
+  (list-of file-like?))
+
+(define (elisp-gexp-or-file-like? val)
+  (or (elisp? val)
+      (list? val)                       ; Allow using s-exps internally.
+      (gexp? val)
+      (file-like? val)))
+
+(define list-of-elisp-gexp-or-file-likes?
+  (list-of elisp-gexp-or-file-like?))
+
+(define-configuration/no-serialization emacs-server
+  (name
+   (string)
+   "A string naming the server.  Users will subsequently be able to start the
+new server by using the command @code{herd start emacs-@var{name}}.  To create
+Emacs client frames for the sever, users can use commands like:
+@code{emacsclient --create-frame --socket-name=@var{name}}.
+
+Because this string is meant for use in shell commands (and filenames), it
+should not contain any characters other than letters and digits and the
+characters @samp{-}, @samp{_}, and @samp{.}."
+   (sanitizer
+    (lambda (str)
+      (cond
+       ((not (string? str))
+        (configuration-field-error #f 'name str))
+       ((string-any (char-set-complement
+                     (char-set-union char-set:letter+digit
+                                     (char-set #\- #\_ #\.)))
+                    str)
+        (configuration-field-error #f 'name str))
+       (else str)))))
+  (inherit-user-directory?
+   (boolean #t)
+   "Whether the server should share its Emacs user directory with that of
+the Emacs Home service.  When false, the server will use a subdirectory
+of the one used by the service for its own user directory.  When true
+(the default), the @code{user-emacs-directory} Emacs variable for the server
+will be set to that of the Emacs Home service, but the server will still load
+its own @file{early-init.el} and @file{init.el} files.  See the
+@code{inherit-init-files?} field for how to inherit configuration from other
+Emacsen.")
+  (inherit-init-files?
+   (boolean #t)
+   "Whether to load the default configuration used by the Emacs Home service,
+that is, the initialization expressions specified by the
+@code{home-emacs-configuration} value for the Emacs Home service.  These are
+loaded in addition to any configuration specified for this specific server.
+
+Note that if this field is true and @code{inherit-user-directory?} is false,
+duplicate copies of of any files specified by the @code{user-files} field of
+the @code{home-emacs-configuration} value for the service are created in the
+Emacs user directory for the server.  This ensures that any references to
+those files in the inherited configuration expressions will not fail in
+unexpected ways.")
+  (auto-start?
+   (boolean #t)
+   "Whether to start the server automatically.")
+  (debug?
+   (boolean #f)
+   "Whether to enable the Emacs Lisp debugger for errors in the initialization
+files of the server.")
+  (shepherd-requirements
+   (list-of-symbols '())
+   "A list of symbols specifying Shepherd services that must be started before
+the service for the Emacs server can be started (@pxref{Defining Services,,,
+shepherd, The GNU Shepherd Manual}).")
+  (environment-variables
+   (gexp #~(default-environment-variables))
+   "A G-expression specifying a list environment variables for the Emacs server.
+The value of this field, if set, will override all default environment
+variables, and users should likely specify a G-expression whose expansion
+includes elements from @code{default-environment-variables} (@pxref{Service
+De- and Constructors,,, shepherd, The GNU Shepherd Manual}).")
+  (load-custom?
+   (boolean #t)
+   "Whether to load customizations created with the Emacs customization
+interface.  When @code{inherit-directory?} is true, customizations made within
+this specific server affect other Emacsen, and vice versa.  Otherwise, the
+server has its own separate set of customizations.")
+  (early-init-file
+   (list-of-elisp-gexp-or-file-likes '())
+   "A list of Elisp expressions, G-expressions, or file-like objects to
+serialize to the Emacs early init file, the @file{early-init.el} file in the
+server's Emacs configuration directory.  For file-like objects that represent
+actual files in the store (for example, those returned by @code{local-file} or
+@code{plain-file}), the contents of each file are serialized.
+
+When the @code{inherit-init-files?} for the server is true, configuration
+specified here is serialized to the early init file after any early init file
+configuration expressions specified by the Emacs Home service type.")
+  (init-file
+   (list-of-elisp-gexp-or-file-likes '())
+   "A list Elisp expressions, G-expressions, or file-like objects to serialize
+to the Emacs user initialization file, the @file{init.el} file in the server's
+Emacs configuration directory.  For file-like objects that represent actual
+files in the store (for example, those returned by @code{local-file} or
+@code{plain-file}), the contents of each file are serialized.
+
+When the @code{inherit-init-files?} for the server is true, configuration
+specified here is serialized to the Emacs user initialization file after the
+any user initialization file configuration expressions specified by the Emacs
+Home service type.")
+  (user-files
+   (alist '())
+   "An association list of filenames and file-like objects specifying files to
+create in the server's Emacs user directory.  For each entry, a file with the
+given filename will be created with the contents of the file-like object.  If
+a list of file-like objects is given for an entry, the new file will contain
+the combined text contents of all of the file-like objects in the list.  This
+field can be used to add configuration files for Emacs that should not be
+automatically loaded when Emacs is initialized.
+
+Note that the Emacs user directory for the server may not be the same as the
+directory containing its Emacs user initialization file.
+
+When the @code{inherit-user-directory?} field is false, user files are created
+in a subdirectory of the user directory used by the Emacs Home service, which
+will be populated with copies of all of files specified in the
+@code{user-files} field of the @code{home-emacs-configuration} value for the
+service."
+   (sanitizer (alist-sanitizer user-files
+                               ((? string?)
+                                . (or (? file-like?)
+                                      (? list-of-file-likes?)))))))
+
+(define list-of-emacs-servers?
+  (list-of emacs-server?))
+
+(define-configuration/no-serialization home-emacs-configuration
+  (emacs
+   (package %default-emacs)
+   "The package providing the @file{/bin/emacs} command.")
+  (packages
+   (list-of-file-likes '())
+   "A list of additional Emacs-related packages to install.")
+  (user-emacs-directory
+   (string %default-emacs-config-dir)
+   "Directory beneath which additional Emacs user files are placed.
+By default, this is also the directory that contains the @file{init.el} and
+@file{early-init.el} Emacs initialization files, but you can change this field
+to specify any directory of your choosing; initialization files generated by
+this service will still be loaded."
+   (sanitizer
+    (lambda (str)
+      ;; Ensure that the path name ends with a '/', as some low-level Emacs
+      ;; libraries use the value of 'user-emacs-directory' with this
+      ;; expectation.
+      (cond
+       ((not (string? str))
+        (configuration-field-error #f 'user-emacs-directory str))
+       ((not (string-suffix? "/" str))
+        (string-append str "/"))
+       (else str)))))
+  (native-compile?
+   (boolean #f)
+   "Whether to enable native-compilation of Emacs packages by building them
+with the Emacs specified by the @code{emacs} field rather than
+@code{emacs-minimal}.")
+  (indent-forms
+   (alist '())
+   "An association list of symbols and indentation rules.  Each entry is of
+the form (@var{symbol} .  @var{indent}), where @var{symbol} is a symbol and
+@var{indent} is an integer.
+
+When @var{symbol} occurs at the beginning of a list in an Emacs Lisp file, the
+first @var{indent} expressions are indented as arguments and the remainder as
+body expressions, as if @var{indent} was supplied as the
+@code{lisp-indent-function} symbol property for @var{symbol} in Emacs.
+Argument expressions are either printed on the same line as @var{symbol} or
+indented 4 columns beyond the base indentation of the enclosing list, and body
+expressions are indented 2 columns beyond the base indentation."
+   (sanitizer (alist-sanitizer indent-forms
+                               ((? symbol?) . (? integer?)))))
+  (load-custom?
+   (boolean #t)
+   "Whether to load customizations created with the Emacs customization
+interface.  Because all configuration files created by this service are
+effectively read-only, the service modifies the default behavior of Emacs so
+that customizations are always saved in a separate @file{custom.el} file,
+which will be loaded when Emacs is initialized if this field is true.")
+  (early-init-file
+   (list-of-elisp-gexp-or-file-likes '())
+   "A list of Elisp expressions, G-expressions, or file-like objects to
+serialize to the Emacs early init file, the @file{early-init.el} file in the
+appropriate Emacs configuration directory.  For file-like objects that
+represent actual files in the store (for example, those returned by
+@code{local-file} or @code{plain-file}), the contents of each file are
+serialized.
+
+The Emacs home service automatically serializes some s-expressions to the
+early initialization file in order to ensure that the Emacs user directory is
+properly set according to the @code{user-emacs-directory} field.  Any
+expressions specified in this field are serialized before the automatically
+added expressions.  This comes with a trade-off: one the one hand, you can
+leverage this fact to insert header comments or a license preamble at the
+beginning of the file, where it would be expected, but on the other hand, the
+Emacs user initialization directory will not be set until after any
+s-expressions specified in this field have already been evaluated.")
+  (init-file
+   (list-of-elisp-gexp-or-file-likes '())
+   "A list additional of Elisp expressions, G-expressions, or file-like
+objects to serialize to the Emacs user initialization file, the @file{init.el}
+file in the appropriate Emacs configuration directory.  For file-like objects
+that represent actual files in the store (for example, those returned by
+@code{local-file} or @code{plain-file}), the contents of each file are
+serialized.
+
+As with the @code{early-init-file} field, some expressions are automatically
+serialized to the Emacs user initialization file by the Emacs home service,
+particularly those to set up the @file{custom.el} file (see the
+@code{load-custom?} field).  Expressions specified using this field are
+serialized before any automatically added expressions.  This means that you
+can specify header comments or license preambles that will occur at the
+beginning of the file, but that the @file{custom.el} file will be loaded after
+all expressions in this field are evaluated.")
+  (user-files
+   (alist '())
+   "An association list of filenames and file-like objects specifying files to
+create in the Emacs user directory.  For each entry, a file with the given
+filename will be created with the contents of the file-like object.  If a list
+of file-like objects is given for an entry, the new file will contain the
+combined text contents of all of the file-like objects in the list.  This
+field can be used to add configuration files for Emacs that should not be
+automatically loaded when Emacs is initialized.
+
+Note that the Emacs user directory, which can be specified using the
+@code{user-emacs-directory} field, may not be the same as the directory
+containing the Emacs user initialization file."
+   (sanitizer (alist-sanitizer user-files
+                               ((? string?)
+                                . (or (? file-like?)
+                                      (? list-of-file-likes?))))))
+  (servers
+   (list-of-emacs-servers '())
+   "A list of configurations for Emacs servers."))
+
+(define-configuration/no-serialization home-emacs-extension
+  (packages
+   (list-of-file-likes '())
+   "A list of additional Emacs-related packages to install.")
+  (indent-forms
+   (alist '())
+   "An association list of symbols and indentation rules.  Each entry is of
+the form (@var{symbol} . @var{indent}), where @var{symbol} is a symbol and
+@var{indent} is an integer specifying the number of argument expressions for
+@var{symbol}."
+   (sanitizer (alist-sanitizer indent-forms
+                               ((? symbol?) . (? integer?)))))
+  (early-init-file
+   (list-of-elisp-gexp-or-file-likes '())
+   "A list of Elisp expressions, G-expressions, or file-like objects to
+serialize to the Emacs early init file, the @file{early-init.el} file in the
+Emacs configuration directory.")
+  (init-file
+   (list-of-elisp-gexp-or-file-likes '())
+   "A list Elisp expressions, G-expressions, or file-like objects to serialize
+to the Emacs user initialization file, the @file{init.el} file in the Emacs
+configuration directory.")
+  (user-files
+   (alist '())
+   "An association list of filenames and file-like objects specifying files to
+create in the Emacs user directory.
+
+Note that the Emacs user directory may not be the same as the directory
+containing the Emacs user initialization file."
+   (sanitizer (alist-sanitizer user-files
+                               ((? string?)
+                                . (or (? file-like?)
+                                      (? list-of-file-likes?))))))
+  (servers
+   (list-of-emacs-servers '())
+   "A list of configurations for Emacs servers.  You cannot specify multiple
+@code{emacs-server} objects with equivalent @code{name} fields."))
+
+(define (server-name->file-name name)
+  "Return the full name for server NAME as a filename."
+  (string-append "emacs-" (string-delete (char-set #\/ #\nul) name)))
+
+(define (emacs-server->provision config)
+  "Return the provision symbol for the Shepherd service created for
+@code{emacs-server} object CONFIG."
+  (match-record config <emacs-server>
+                (name)
+    (string->symbol (server-name->file-name name))))
+
+(define (server-user-directory name user-emacs-directory)
+  "Return the location of the Emacs user directory for server NAME based on
+the USER-EMACS-DIRECTORY from the Emacs home service."
+  (string-append (file-name-concat user-emacs-directory
+                                   (server-name->file-name name))
+                 "/"))
+
+(define (home-emacs-packages config)
+  "Return a list of file-like objects to install from CONFIG."
+  ;; XXX: This can cause some unforeseen problems, since most packages have
+  ;; not been tested with input rewriting.
+  (define (rewrite-for-native-compile emacs)
+    (package-input-rewriting
+     `((,emacs-minimal . ,emacs))))
+
+  (match-record config <home-emacs-configuration>
+                (emacs
+                 native-compile?
+                 packages)
+    (append (list emacs)
+            (if native-compile?
+                (map (rewrite-for-native-compile emacs)
+                     (delete-duplicates packages eq?))
+                packages))))
+
+(define (home-emacs-xdg-configuration-files config)
+  "Return from CONFIG an association list of filenames and file-like objects
+to create in XDG_CONFIG_HOME."
+  (define (elisp-file-with-forms name exps)
+    (composite-elisp-file name exps
+                          #:special-forms
+                          (home-emacs-configuration-indent-forms config)))
+
+  (define (set-user-emacs-directory-sexps directory)
+    (list `(setq user-emacs-directory ,directory)
+          ;; Reset variables set before early init file is loaded that rely
+          ;; upon the value of `user-emacs-directory'.
+          ;;
+          ;; XXX: 'native-comp-eln-load-path' is properly set in startup.el to
+          ;; reflect the new 'user-emacs-directory', but this means that
+          ;; servers which use their own 'user-emacs-directory' get their own
+          ;; eln cache.
+          '(custom-reevaluate-setting 'auto-save-list-file-prefix)
+          '(custom-reevaluate-setting 'package-user-dir)
+          '(custom-reevaluate-setting 'package-quickstart-file)
+          '(custom-reevaluate-setting 'abbrev-file-name)
+          '(custom-reevaluate-setting 'custom-theme-directory)))
+
+  (define (load-custom?-sexps load-custom?)
+    ;; 'locate-user-emacs-file' also ensures that 'user-emacs-directory'
+    ;; exists, creating it with the proper permissions as a side effect.
+    (list '(setq custom-file (locate-user-emacs-file "custom.el"))
+          (if load-custom?
+              '(if (not (file-exists-p custom-file))
+                   (make-empty-file custom-file)
+                   (load custom-file))
+              '(when (not (file-exists-p custom-file))
+                 (make-empty-file custom-file)))
+          (elisp (unelisp-newline))))
+
+  (define config-user-emacs-directory
+    (home-emacs-configuration-user-emacs-directory config))
+
+  (define (server->config-file-entries server
+                                       inherited-early-init-file
+                                       inherited-init-file)
+    (match-record server <emacs-server>
+                  (name
+                   inherit-init-files?
+                   inherit-user-directory?
+                   load-custom?
+                   early-init-file
+                   init-file)
+      (let ((server-dir (server-name->file-name name)))
+        (list (list (emacs-config-filename
+                     (file-name-concat server-dir %emacs-early-init-filename))
+                    (elisp-file-with-forms %emacs-early-init-filename
+                                           (append
+                                            (set-user-emacs-directory-sexps
+                                             (if inherit-user-directory?
+                                                 config-user-emacs-directory
+                                                 (server-user-directory
+                                                  name
+                                                  config-user-emacs-directory)))
+                                            (if inherit-init-files?
+                                                inherited-early-init-file
+                                                '())
+                                            early-init-file)))
+              (list (emacs-config-filename
+                     (file-name-concat server-dir %emacs-user-init-filename))
+                    (elisp-file-with-forms %emacs-user-init-filename
+                                           (append
+                                            (if inherit-init-files?
+                                                inherited-init-file
+                                                '())
+                                            init-file
+                                            (load-custom?-sexps load-custom?))))))))
+
+  (match-record config <home-emacs-configuration>
+                (user-emacs-directory
+                 load-custom?
+                 early-init-file
+                 init-file
+                 servers)
+    (append (list (list (emacs-config-filename %emacs-early-init-filename)
+                        (elisp-file-with-forms %emacs-early-init-filename
+                                               (append
+                                                early-init-file
+                                                (set-user-emacs-directory-sexps
+                                                 user-emacs-directory))))
+                  (list (emacs-config-filename %emacs-user-init-filename)
+                        (elisp-file-with-forms %emacs-user-init-filename
+                                               (append init-file
+                                                       (load-custom?-sexps
+                                                        load-custom?)))))
+            (append-map (cut server->config-file-entries <>
+                             early-init-file
+                             init-file)
+                        servers))))
+
+(define (home-emacs-files config)
+  "Return from CONFIG an association list of filenames and file-like objects
+to create in the Emacs user directory."
+
+  (define file-name-with-home (make-regexp "^(~/|/home/[^/]+/)(.+)$"))
+
+  (define (file-name->home-file-name filename)
+    (or (and=> (regexp-exec file-name-with-home filename)
+               (cut match:substring <> 2))
+        filename))
+
+  (define (user-file->home-files-entry entry directory)
+    (match entry
+      ((name . files)
+       (list (file-name-concat directory name)
+             (apply composite-file (basename name) (ensure-list files))))))
+
+  (define (server->home-files-entries server directory)
+    (match-record server <emacs-server>
+                  (name
+                   inherit-user-directory?
+                   inherit-init-files?
+                   user-files)
+      (let ((server-dir (if inherit-user-directory?
+                            directory
+                            (server-user-directory name directory))))
+        (map (cut user-file->home-files-entry <> server-dir)
+             (append user-files
+                     (if (and inherit-init-files?
+                              (not inherit-user-directory?))
+                         (home-emacs-configuration-user-files config)
+                         '()))))))
+
+  (match-record config <home-emacs-configuration>
+                (user-emacs-directory
+                 user-files
+                 servers)
+    (let ((user-emacs-directory* (file-name->home-file-name
+                                  user-emacs-directory)))
+      (append (map (cut user-file->home-files-entry <> user-emacs-directory*)
+                   user-files)
+              (append-map (cut server->home-files-entries <>
+                               user-emacs-directory*)
+                          servers)))))
+
+(define (home-emacs-shepherd-services config)
+  "Return a list of Shepherd services for CONFIG."
+  (match-record config <home-emacs-configuration>
+                (emacs user-emacs-directory servers)
+    (map
+     (lambda (server)
+       (match-record server <emacs-server>
+                     (name
+                      inherit-user-directory?
+                      auto-start?
+                      debug?
+                      shepherd-requirements
+                      environment-variables)
+         (let ((server-init-dir (file-name-concat
+                                 %default-emacs-config-dir
+                                 (server-name->file-name name)))
+               (server-user-dir (if inherit-user-directory?
+                                    user-emacs-directory
+                                    (server-user-directory
+                                     name user-emacs-directory))))
+           (shepherd-service
+            (provision (list (emacs-server->provision server)))
+            (requirement shepherd-requirements)
+            (start
+             #~(make-forkexec-constructor
+                (list #$(file-append emacs "/bin/emacs")
+                      #$(string-append "--init-directory=" server-init-dir)
+                      #$(string-append "--fg-daemon=" name)
+                      #$@(if debug?
+                             (list "--debug-init")
+                             '()))
+                #:environment-variables
+                #$environment-variables
+                #:log-file
+                #$(file-name-concat server-user-dir
+                                    (string-append
+                                     (server-name->file-name name) ".log"))))
+            (stop
+             #~(make-forkexec-constructor
+                (list #$(file-append emacs "/bin/emacsclient")
+                      "-s" #$name "--eval" "(kill-emacs)")))
+            (actions (list
+                      (shepherd-configuration-action
+                       (file-name-concat server-init-dir
+                                         %emacs-user-init-filename))))
+            (auto-start? auto-start?)
+            (documentation
+             (string-append "Start the Emacs server called "
+                            name "."))))))
+     servers)))
+
+(define (home-emacs-extensions original extensions)
+  "Extend the Emacs home service configuration ORIGINAL with list of
+configurations EXTENSIONS."
+  (match-record original <home-emacs-configuration>
+                (packages
+                 indent-forms
+                 early-init-file
+                 init-file
+                 user-files
+                 servers)
+    (home-emacs-configuration
+     (inherit original)
+     (packages (append packages
+                       (append-map home-emacs-extension-packages
+                                   extensions)))
+     (indent-forms (append indent-forms
+                           (append-map home-emacs-extension-indent-forms
+                                       extensions)))
+     (early-init-file (append early-init-file
+                              (append-map home-emacs-extension-early-init-file
+                                          extensions)))
+     (init-file (append init-file
+                        (append-map home-emacs-extension-init-file
+                                    extensions)))
+     (user-files (merge-alists user-files
+                              (append-map home-emacs-extension-user-files
+                                          extensions)))
+     ;; XXX: Does not handle servers with duplicate names.  Will Shepherd
+     ;; signal an error to notify the user?
+     (servers (append servers
+                      (append-map home-emacs-extension-servers
+                                  extensions))))))
+
+(define home-emacs-service-type
+  (service-type (name 'home-emacs-service)
+                (extensions
+                 (list (service-extension
+                        home-profile-service-type
+                        home-emacs-packages)
+                       (service-extension
+                        home-xdg-configuration-files-service-type
+                        home-emacs-xdg-configuration-files)
+                       (service-extension
+                        home-files-service-type
+                        home-emacs-files)
+                       (service-extension
+                        home-shepherd-service-type
+                        home-emacs-shepherd-services)))
+                (default-value (home-emacs-configuration))
+                (compose identity)
+                (extend home-emacs-extensions)
+                (description
+                 "Configure and run the GNU Emacs extensible text editor.")))
+
+
+;;;
+;;; Emacs packages home service
+;;;
+
+(define %emacs-extra-init-files-path %default-emacs-config-dir)
+(define %emacs-extra-init-files-path-variable 'guix--extra-files-load-directory)
+
+(define (elisp-or-gexp? val)
+  (or (elisp? val)
+      (gexp? val)))
+
+(define (keyword-or-symbol? val)
+  (or (keyword? val)
+      (symbol? val)))
+
+(define (package-or-null? val)
+  (or (package? val)
+      (null? val)))
+
+(define (string-or-file-like? val)
+  (or (string? val)
+      (file-like? val)))
+
+(define (string-or-vector? val)
+  (or (string? val)
+      (vector? val)))
+
+(define (symbol-or-false? val)
+  (or (symbol? val)
+      (not val)))
+
+(define list-of-elisp-or-gexps?
+  (list-of elisp-or-gexp?))
+
+(define list-of-string-or-file-likes?
+  (list-of string-or-file-like?))
+
+(define (elispifiable-quoted? val)
+  "Return whether VAL can be serialized as Elisp, but needs to be quoted."
+  (or (symbol? val)
+      (pair? val)))
+
+(define (elispifiable? val)
+  "Return whether VAL can be serialized as Elisp."
+  (or (constant? val)
+      (elispifiable-quoted? val)
+      (gexp? val)
+      (file-like? val)
+      (and (elisp? val)
+           (not (blank? (elisp->sexp val))))))
+
+(define elispifiable->elisp
+  (match-lambda
+    ((? elisp? obj)
+     obj)
+    ((? elispifiable-quoted? obj)
+     (sexp->elisp `(quote ,obj)))
+    (obj
+     (sexp->elisp obj))))
+
+(define-syntax keys-field-sanitizer
+  (syntax-rules ()
+    ((_ field-name)
+     (alist-sanitizer field-name
+                      ((? string-or-vector?) . (? symbol-or-false?))))))
+
+(define-configuration/no-serialization emacs-keymap
+  (name
+   (symbol 'global-map)
+   "The symbol of the Emacs keymap in which to bind keys.")
+  (package-name
+   (symbol-or-false #f)
+   "The symbol naming the Emacs package providing the keymap, as would be used
+with Emacs @code{require}.  If this field is false (the default), then the
+package for which the keymap is being configured should define the keymap or
+the keymap should otherwise be defined by the time the configuration for the
+package is evaluated.  Note that when the @code{prefix?} field is true, this
+implies that the keymap will be defined and this field has no effect.")
+  (prefix?
+   (boolean #f)
+   "Whether to create a new keymap and assign it to a prefix command named
+@code{name}.  Specifically, the new keymap is assigned to the function
+definition of the symbol @code{name}.  This is useful when the keymap does not
+already exist, but the user would like to bind it to a prefix key using the
+@code{keys-global} field in an @code{emacs-package} configuration object or
+even using the @code{keys} field in another @code{emacs-keymap} object.
+Because a prefix command is a valid function, no special handling is needed to
+use it in keybinding definitions where commands are expected.  Another benefit
+of prefix commands comes when using keybinding introspection features such as
+@code{which-key-mode}: @code{name} will be displayed as a prefix key
+description when it is bound to a prefix command declared in this way, whereas
+ad-hoc prefix key bindings like @kbd{C-c d d} where @kbd{C-c d} is not
+explicitly defined will not have explicitly meaningful descriptions.
+
+Note that when this field true, the @code{package-name} field has no effect.")
+  (repeat?
+   (boolean #f)
+   "Whether to make this keymap a repeat map (@pxref{Repeating,,, emacs, The
+GNU Emacs Manual}).  Repeat maps are created by setting the @code{repeat-map}
+symbol property for each key definition in @code{keys} to the @code{name} of
+this keymap.  Use the @code{repeat-exit} field to override this setting for
+specific bindings.")
+  (repeat-exit
+   (list-of-symbols '())
+   "A list of commands that exit the repeat map.  When @code{repeat?} is true,
+these commands do not get the @code{repeat-map} property.  The meaning of this
+field is similar to that of the @code{:exit} keyword used by the
+@code{defvar-keymap} function in Emacs.  This field has no effect when
+@code{repeat?} is false.")
+  (repeat-enter
+   (list-of-symbols '())
+   "A list of additional commands that enter the repeat map.  When
+@code{repeat?} is true, these commands get the @code{repeat-map} property,
+even when they are not bound in the keymap.  This is only useful when a
+command is not bound in @code{name}, but the repeat map should be accessible
+after that command is invoked (e.g., with @kbd{M-x}).  The meaning of this
+field is similar to that of the @code{:enter} keyword used by the
+@code{defvar-keymap} function in Emacs.  This field has no effect when
+@code{repeat?} is false.")
+  (disabled-commands
+   (alist '())
+   "An association list of command symbols and whether to disable them.  When
+a disabled command is interactively invoked, Emacs asks for confirmation from
+the user (@pxref{Disabling,,, emacs, The GNU Emacs Manual}).  The values of
+this alist should be booleans, which will be stored as the value of the
+@code{disabled} property of each respective command symbol.  Thus, to disable
+the @code{transpose-chars} command and enable the @code{erase-buffer} command,
+you can use:
+
+@lisp
+'((transpose-chars . #t)
+  (erase-buffer . #f))
+@end lisp
+"
+   (sanitizer
+    (alist-sanitizer disabled-commands
+                     ((? symbol?) . (? boolean?)))))
+  (keys
+   (alist '())
+   "An association list of key sequences and binding definitions.  Key
+sequences are Emacs-specific string or vector representations of sequences of
+keystrokes or events.  Strings should be valid arguments to the Emacs function
+@code{kbd}, and they are preferred over the low-level vector
+representations (@pxref{Keymaps,,, elisp, The Emacs Lisp Manual}).  Binding
+definitions should be Emacs command symbols.  As a special case, when a
+binding definition is the boolean false, the key is unset in the keymap."
+   (sanitizer (keys-field-sanitizer keys))))
+
+(define list-of-emacs-keymaps?
+  (list-of emacs-keymap?))
+
+(define-configuration/no-serialization emacs-package
+  (name
+   (symbol)
+   "The symbol naming the Emacs package or library, as would be used with
+Emacs @code{require}.")
+  (package
+    (package-or-null '())
+    "A Guix package providing the Emacs package specified by @code{name}.  If
+the package is built into Emacs, or if there is no associated Guix package,
+this field should be set to the empty list (the default).")
+  (extra-packages
+   (list-of-packages '())
+   "A list of packages that provide additional functionality used by this
+package, but which are not installed automatically by the Guix package manager
+as propagated inputs of @code{package}.")
+  (install?
+   (boolean #t)
+   "Whether to install @code{package} and @code{extra-packages}.")
+  (extra-init-files
+   (alist '())
+   "An association list of filenames and file-like objects containing Emacs
+Lisp to load when Emacs is initialized.  For each entry, a file with the text
+contents of the file-like object, or the combined text contents of all of the
+file-like objects in a list if a list is specified, will be created with the
+given filename in the appropriate Emacs configuration directory (the directory
+where the @file{early-init.el} and @file{init.el} files are located).  These
+files will then be loaded when Emacs is initialized.
+
+This can be used as an escape hatch, similarly to the @code{extra-init} field.
+It is useful when large amounts of code must be loaded in order to configure a
+package.
+
+Note that it is an error to specify files with the filenames @samp{init.el}
+and @samp{early-init.el}, because these files are already generated by the
+Emacs home service.
+
+Also note that it is up to the package serializer whether and when to load
+these files, although the @code{home-emacs-service-type} ensures that the
+files will be present in the relevant configuration directories.
+@code{%emacs-simple-package-serializer} and
+@code{%emacs-use-package-serializer} both load the files at initialization
+time, before any expressions given in the @code{extra-init} field, regardless
+of the value of the @code{load-after-packages} field."
+   (sanitizer (alist-sanitizer extra-init-files
+                               ((? string?)
+                                . (or (? file-like?)
+                                      (? list-of-file-likes?))))))
+  (user-files
+   (alist '())
+   "An association list of filenames and file-like objects specifying files to
+create in the Emacs user directory.  For each entry, a file with the given
+filename will be created in the Emacs user directory with the contents of the
+file-like object.  If a list of file-like objects is given for an entry, the
+new file will contain the combined text contents of all of the file-like
+objects in the list.  This field should be used to add per-package files to
+the Emacs user directory."
+   (sanitizer (alist-sanitizer user-files
+                               ((? string?)
+                                . (or (? file-like?)
+                                      (? list-of-file-likes?))))))
+  (load-force?
+   (boolean #f)
+   "Whether to force loading of this package immediately when Emacs is
+initialized, rather than deferring loading, for example, until an autoloaded
+function is invoked.  This is similar in effect to the keyword @code{:demand}
+from @code{use-package} and to the inverse of the keyword @code{:defer}.  The
+difference is that when this field is false, package loading should always be
+deferred; @code{use-package} normally does not defer loading when it does not
+set up autoloads, because it doesn't know that Guix handles autoloads on its
+own.")
+  (load-predicates
+   (list-of-elisp-or-gexps '())
+   "A list predicate expressions to evaluate when Emacs is initialized to
+determine whether to evaluate the configuration for this package.  When this
+list is not empty, @emph{all} other configuration for this package should be
+effectively surrounded in the Emacs user initialization file by a block of the
+form: @code{(when @var{load-predicates} @dots{})}.  This is the supercharged
+Guix version of the @code{use-package} @code{:if} keyword!
+
+If multiple load predicates are specified, the behavior is determined by the
+package configuration serializer.  Both
+@code{%emacs-simple-package-serializer} and
+@code{%emacs-use-package-serializer} compose load predicates using @code{and},
+so that all load predicates in the list must be satisfied in order for the
+package configuration to be evaluated.")
+  (load-after-packages
+   (list-of-symbols '())
+   "A list of symbols for Emacs packages that must be loaded before this
+package is loaded.  Only after all of the packages in the list have been
+loaded by Emacs should configuration for this package be evaluated.  This is
+similar to a simplified version of the @code{:after} keyword from
+@code{use-package}.")
+  (load-paths
+   (list-of-string-or-file-likes '())
+   "A list of additional load paths to add to the Emacs @code{load-paths}
+variable.  Load paths can be specified either as strings or as file-like
+objects, in which case a path to the respective store item is substituted.")
+  (autoloads
+   (list-of-symbols '())
+   "A list of Emacs functions from the package to autoload.  This can be
+useful, for example, when defining custom commands in the Emacs user
+initialization file that use functions which are not autoloaded by default.")
+  (autoloads-interactive
+   (list-of-symbols '())
+   "A list of additional Emacs interactive commands from the package to
+autoload, so that they can be invoked interactively before the package is
+loaded.")
+  (keys-global
+   (alist '())
+   "An association list of key sequences (as strings or vectors) and Emacs
+commands to bind in the global keymap."
+   (sanitizer (keys-field-sanitizer keys-global)))
+  (keys-global-keymaps
+   (alist '())
+   "An association list of key sequences and Emacs keymap variables to bind to
+them in the global keymap.  The keymap variables should be symbols that define
+keymaps in the package; they can be effectively autoloaded using this
+assumption."
+   (sanitizer (alist-sanitizer field-name
+                               ((? string-or-vector?) . (? symbol?)))))
+  (keys-override
+   (alist '())
+   "An association list of key sequences and symbols naming Emacs commands to
+bind in the global override map.  These key bindings have a higher precedence
+than local and global keybindings."
+   (sanitizer (keys-field-sanitizer keys-override)))
+  (keys-local
+   (list-of-emacs-keymaps '())
+   "A list of key binding configurations for specific keymaps, each contained
+in an @code{emacs-keymap} object.")
+  (options
+   (alist '())
+   "An association list of user options and values for this package.  Options
+should be symbols naming Emacs variables, and values can be any object that
+can be serialized to Elisp.  For values, primitive Scheme data types are
+implicitly quoted, including lists and symbols.  To instead set an option to
+the value of an expression to be evaluated at Emacs initialization time,
+either use an Elisp expression (e.g., specified with the @code{elisp} form) or
+a G-expression for a value."
+   (sanitizer (alist-sanitizer options
+                               ((? symbol?) . (? elispifiable?)))))
+  (faces
+   (alist '())
+   "An association list of face symbols and face specs.  @xref{Defining
+Faces,,, elisp, The Emacs Lisp Manual} for the format of face specs."
+   (sanitizer (alist-sanitizer
+               faces
+               ((? symbol?)
+                . (((or 'default #t 't (? list?)) . (prop . rest)) ..1)))))
+  (hooks
+   (alist '())
+   "An association list of hooks and functions to add to them.  Each entry is
+a pair of symbols.  Hook symbols in Emacs should end in @samp{-hook}, but the
+@code{%emacs-simple-package-serializer} and
+@code{%emacs-use-package-serializer} serializers effectively add this suffix
+when necessary."
+   (sanitizer (alist-sanitizer hooks
+                               ((? symbol?) . (? symbol?)))))
+  (auto-modes
+   (alist '())
+   "An association list of filename patterns as regular expression strings and
+Emacs mode functions to call when visiting files with filenames that match the
+patterns.  @xref{Auto Major Mode,,, elisp, The Emacs Lisp Manual} for
+details."
+   (sanitizer (alist-sanitizer auto-modes
+                               ((? string?) . (? symbol?)))))
+  (magic-modes
+   (alist '())
+   "An association list regular expression strings and Emacs mode functions to
+call when visiting files that begin with matching text.  @xref{Auto Major
+Mode,,, elisp, The Emacs Lisp Manual} for details."
+   (sanitizer (alist-sanitizer magic-modes
+                               ((? string?) . (? symbol?)))))
+  (extra-after-load
+   (list-of-elisp-or-gexps '())
+   "A list of Elisp expressions or G-expressions to evaluate after the package
+is loaded, as with the Emacs @code{eval-after-load} function.  Elisp
+expressions can be specified using the @code{elisp} and @code{elisp*} forms.")
+  (extra-init
+   (list-of-elisp-or-gexps '())
+   "A list of Elisp expressions or G-expressions to evaluate immediately when
+Emacs is initialized, even if loading is deferred due to the
+@code{load-force?} field.  Note that the @code{load-predicates} field should
+still determine whether these expressions are evaluated, and they will only be
+evaluated after all packages specified in the @code{load-after-packages} field
+have been loaded.")
+  (extra-keywords
+   (alist '())
+   "An association list of keys and lists of extra Elisp expressions or
+G-expressions.  Keys can potentially be any keyword or symbol object; keywords
+are automatically serialized to their Emacs Lisp equivalent (e.g.,
+@code{#:keyword} is serialized as @code{:keyword}).  The meanings of entries
+is specific to each package serializer, and any key may be ignored by a
+package serializer.  This field is currently ignored by the
+@code{%emacs-simple-package-serializer}.  Entries in this list matching
+@code{use-package} keywords will be spliced by the
+@code{%emacs-use-package-serializer} into the @code{use-package} body, after
+all other forms."
+   (sanitizer (alist-sanitizer extra-keywords
+                               ((? keyword-or-symbol? key)
+                                . (? list-of-elisp-or-gexps? val))))))
+
+(define list-of-emacs-packages?
+  (list-of emacs-package?))
+
+(define-configuration/no-serialization emacs-package-serializer
+  (name
+   (symbol)
+   "A symbol identifying the serializer.")
+  (procedure
+   (procedure)
+   "A procedure that takes one argument, an @code{emacs-package} object, and
+that should return a list of @code{elisp} objects or G-expressions containing
+configuration to serialize to the Emacs user initialization file.")
+  (dependencies
+   (list-of-packages '())
+   "An list of additional packages to install whenever this
+serializer is used.")
+  (indent-forms
+   (alist '())
+   "An association list of symbols and indentation rules.  Each entry is of
+the form (@var{symbol} .  @var{indent}), where @var{symbol} is a symbol and
+@var{indent} is an integer.  Values have the same effect as the
+@code{indent-forms} field in the @code{home-emacs-configuration} record.
+
+Note that indentation rules specified here will subsequently affect all Emacs
+Lisp expressions serialized by the Emacs home service, not just configuration
+for the Emacs packages Home service"
+   (sanitizer (alist-sanitizer indent-forms
+                               ((? symbol?) . (? integer?)))))
+  (extra-init
+   (list-of-elisp-or-gexps '())
+   "A list additional of Elisp expressions or G-expressions to serialize to
+the Emacs user initialization file before all serialized Emacs package
+configuration.  This is useful for code that must set up or configure the
+package serializer itself.")
+  (description
+   (string "")
+   "A brief description of the serializer."))
+
+(define (compose-load-predicates-lambda composer)
+  "Return a lambda that composes multiple load predicates into a single
+s-expression beginning with symbol COMPOSER."
+  (match-lambda
+    (() '())
+    (lst
+     (if (> (length lst) 1)
+         `(,composer ,@lst)
+         (first lst)))))
+
+(define emacs-package-extra-init-file->sexp
+  ;; XXX: Determining on the Elisp side the directory in which to look for
+  ;; extra init files to load is complicated by the fact that variables like
+  ;; 'user-init-file' (and 'server-name') are apparently not defined until
+  ;; after the init file is evaluated.  We use the Elisp variable named by
+  ;; '%emacs-extra-init-files-path-variable', which is set up by the
+  ;; 'home-emacs-packages-service-type'.  An alternative would be to use
+  ;; 'load-path' to store the config directory, but modifying 'load-path' in a
+  ;; way that is not transparent to the user introduces security concerns.
+  (match-lambda
+    ((name . _)
+     `(load (expand-file-name ,name
+                              ,%emacs-extra-init-files-path-variable)
+            #f #f #t))))
+
+(define (emacs-package-hook->normalized-symbol hook)
+  (let* ((str (symbol->string hook)))
+    (string->symbol
+     (if (not (or (string-suffix? "-hook" str)
+                  (string-suffix? "-functions" str)
+                  ;; Rare, e.g., 'emms-metaplaylist-mode-hooks'.
+                  (string-suffix? "-hooks" str)))
+         (string-append str "-hook")
+         str))))
+
+(define (keys-local->simple-sexp keymap package)
+  "Return an Elisp S-expression that configures <emacs-keymap> object KEYMAP
+for package named by the symbol PACKAGE."
+  (match-record keymap <emacs-keymap>
+                (name
+                 package-name
+                 prefix?
+                 repeat?
+                 repeat-exit
+                 repeat-enter
+                 disabled-commands
+                 keys)
+    (let ((keydefs (append (map (match-lambda
+                                  (((? vector? k) . s)
+                                   `(define-key ,name ,k
+                                      ,(elispifiable->elisp s)))
+                                  (((? string? k) . s)
+                                   ;; Function introduced in Emacs 29.1.
+                                   `(keymap-set ,name ,k
+                                                ,(elispifiable->elisp s))))
+                                keys)
+                           (if repeat?
+                               (map (lambda (s)
+                                      `(put ',s 'repeat-map ',name))
+                                    (delete-duplicates
+                                     (append
+                                      (filter-map (match-lambda
+                                                    ((_ . s)
+                                                     (if (or (not s)
+                                                             (memq s repeat-exit))
+                                                         #f
+                                                         s)))
+                                                  keys)
+                                      repeat-enter)
+                                     eq?))
+                               '()))))
+      (append
+       (if prefix?
+           (list `(progn
+                   (defvar-keymap ,name
+                     `:prefix ',name)
+                   ,@keydefs))
+           (list `(if (boundp ',name)
+                      (progn
+                       ,@keydefs)
+                      (with-eval-after-load
+                          ',(or package-name package)
+                        ,@keydefs))))
+       (map (match-lambda
+              ((command . val)
+               `(put ',command 'disabled ,val)))
+            disabled-commands)))))
+
+(define (emacs-package->simple-elisp config)
+  "Return from 'emacs-package' object CONFIG a list containing Elisp
+expressions that configure Emacs using only minimal built-in functionality."
+  (define (load-path->sexp obj)
+    `(add-to-list 'load-path ,obj))
+
+  (define keys-global->sexp
+    (match-lambda  (((? vector? k) . s)
+                    `(global-set-key ,k ,(elispifiable->elisp s)))
+                   (((? string? k) . s)
+                    ;; Function introduced in Emacs 29.1.
+                    `(keymap-global-set ,k ,(elispifiable->elisp s)))))
+
+  (define keys-override->sexp
+    (match-lambda ((k . s)
+                   `(bind-key* ,k ,(elispifiable->elisp s)))))
+
+  (define option->sexp
+    (match-lambda ((key . val)
+                   ;; Macro introduced in Emacs 29.1.
+                   `(setopt ,key ,(elispifiable->elisp val)))))
+
+  (define face->sexp
+    (match-lambda ((face . spec)
+                   `(face-spec-set ',face ',spec))))
+
+  (define hook->sexp
+    (match-lambda ((hook . func)
+                   (let ((hook* (emacs-package-hook->normalized-symbol hook)))
+                     `(add-hook ',hook* (function ,func))))))
+
+  (define auto-mode->sexp
+    (match-lambda ((pat . mode)
+                   `(add-to-list 'auto-mode-alist '(,pat . ,mode)))))
+
+  (define magic-mode->sexp
+    (match-lambda ((pat . mode)
+                   `(add-to-list 'magic-mode-alist '(,pat . ,mode)))))
+
+  (match-record config <emacs-package>
+                (name
+                 extra-init-files
+                 load-force?
+                 load-predicates
+                 load-after-packages
+                 load-paths
+                 autoloads
+                 autoloads-interactive
+                 keys-global
+                 keys-global-keymaps
+                 keys-override
+                 keys-local
+                 options
+                 faces
+                 hooks
+                 auto-modes
+                 magic-modes
+                 extra-after-load
+                 extra-init
+                 extra-keywords)
+
+    (define (autoload->sexp* obj interactive)
+      `(autoload (function ,obj) ,(symbol->string name) #f ,interactive))
+
+    (define autoload->sexp
+      (cut autoload->sexp* <> #f))
+
+    (define autoload-interactive->sexp
+      (cut autoload->sexp* <> #t))
+
+    (define keys-global-keymaps->sexp
+      (match-lambda (((? vector? ks) . obj)
+                     `(progn
+                       (autoload ',obj
+                                 ,(symbol->string name)
+                                 #f #f 'keymap)
+                       (global-set-key ,ks ,obj)))
+                    (((? string? ks) . obj)
+                     `(progn
+                       (autoload ',obj
+                                 ,(symbol->string name)
+                                 #f #f 'keymap)
+                       (keymap-global-set ,ks ,obj)))))
+
+    (define (load-after-packages->sexp load-after extra)
+      (let loop ((load-after (reverse load-after))
+                 (acc '()))
+        (if (null? load-after)
+            acc
+            (loop (cdr load-after)
+                  (cons 'with-eval-after-load
+                        (cons (list 'quote (car load-after))
+                              (if (null? acc)
+                                  extra
+                                  (list acc))))))))
+
+    (let* ((extra-init-files* (map emacs-package-extra-init-file->sexp
+                                   extra-init-files))
+           (load-predicates* (apply (compose-load-predicates-lambda 'and)
+                                    (list load-predicates)))
+           (load-after-packages* load-after-packages)
+           (load-paths* (map load-path->sexp load-paths))
+           (autoloads* (map autoload->sexp autoloads))
+           (autoloads-interactive* (map autoload-interactive->sexp
+                                        autoloads-interactive))
+           (keys-global* (map keys-global->sexp keys-global))
+           (keys-global-keymaps* (map keys-global-keymaps->sexp
+                                      keys-global-keymaps))
+           (keys-override* (map keys-override->sexp keys-override))
+           (keys-local* (append-map (cut keys-local->simple-sexp <> name)
+                                    keys-local))
+           (options* (map option->sexp options))
+           (faces* (map face->sexp faces))
+           (hooks* (map hook->sexp hooks))
+           (auto-modes* (map auto-mode->sexp auto-modes))
+           (magic-modes* (map magic-mode->sexp magic-modes))
+           (extra-after-load* (cond
+                               (load-force?
+                                (list
+                                 `(if (not (require ',name nil t))
+                                      (display-warning
+                                       'initialization
+                                       (format "Failed to load %s" ',name)
+                                       :error)
+                                      ,@extra-after-load)))
+                               ((not (null? extra-after-load))
+                                (list `(with-eval-after-load
+                                           (quote ,name)
+                                         ,@extra-after-load)))
+                               (else '())))
+           (after-packages-sexps (append autoloads*
+                                         autoloads-interactive*
+                                         keys-global*
+                                         keys-override*
+                                         keys-global-keymaps*
+                                         keys-local*
+                                         options*
+                                         faces*
+                                         hooks*
+                                         auto-modes*
+                                         magic-modes*
+                                         extra-after-load*
+                                         extra-init))
+           (combined-sexps (append load-paths*
+                                   extra-init-files*
+                                   (if (null? load-after-packages*)
+                                       after-packages-sexps
+                                       (list (load-after-packages->sexp
+                                              load-after-packages*
+                                              after-packages-sexps)))))
+           (comment-string (string-append ";;; Package "
+                                          (symbol->string name)
+                                          "\n")))
+      (if (null? combined-sexps)
+          '()
+          (append
+           (list (elisp (unelisp-comment comment-string)))
+           (if (null? load-predicates*)
+               (map sexp->elisp combined-sexps)
+               (list (sexp->elisp `(when ,load-predicates*
+                                     ,@combined-sexps)))))))))
+
+(define %emacs-simple-package-serializer
+  (emacs-package-serializer
+   (name 'emacs-simple-package)
+   (procedure emacs-package->simple-elisp)
+   (description "An Emacs package configuration serializer that configures
+Emacs using minimal, built-in Emacs mechanisms, instead of complex macros such
+as @code{use-package}.")))
+
+(define (emacs-package->use-package-elisp config)
+  "Return from 'emacs-package' object CONFIG a list containing Elisp
+expressions that configures Emacs using the 'use-package' macro."
+  (define-syntax unless-null
+    (syntax-rules ()
+      ((_ var exp)
+       (if (null? var)
+           '()
+           exp))
+      ((_ var)
+       var)))
+
+  (define (keys-local->sexp config)
+    (match-record config <emacs-keymap>
+                  (name package-name prefix? repeat? repeat-exit keys)
+      (cond
+       ;; Skip adding a ':bind' keyword when there is a 'package-name',
+       ;; because 'use-package' isn't smart enough to (eval-after-)load it
+       ;; when the keymap is defined in another package.  Also skip adding the
+       ;; keyword when there is a true 'prefix?' field, because 'use-package'
+       ;; (more specifically, 'bind-keys') isn't smart enough to define
+       ;; non-existent keymaps either.
+       ((or package-name prefix? (null? keys)) '())
+       (repeat?
+        (receive (exit rest)
+            (partition (match-lambda
+                         ((_ . binding)
+                          (memq binding repeat-exit)))
+                       keys)
+          `(:repeat-map ,name
+            ,@rest
+            ,@(if (null? exit)
+                  '()
+                  `(:exit
+                    ,(elisp (unelisp-newline))
+                    ,@exit)))))
+       (else `(:map ,name
+               ,@keys)))))
+
+  (define (keys-local->extra-sexps keymap)
+    (match-record keymap <emacs-keymap> (package-name prefix?)
+      (if (or package-name prefix?)
+          (keys-local->simple-sexp keymap
+                                   (emacs-package-name config))
+          '())))
+
+  (define option->sexp
+    (match-lambda ((key . val)
+                   `(,key ,(elispifiable->elisp val)))))
+
+  (define face->sexp
+    (match-lambda ((key . val)
+                   `(,key ,val))))
+
+  (define hook->sexp
+    (match-lambda ((hook . func)
+                   (let ((hook*
+                          (emacs-package-hook->normalized-symbol hook)))
+                     `(,hook* . ,func)))))
+
+  (define use-package-keywords '(#:after
+                                 #:autoload
+                                 #:bind
+                                 #:bind*
+                                 #:bind-keymap
+                                 #:bind-keymap*
+                                 #:catch
+                                 #:commands
+                                 #:config
+                                 #:custom
+                                 #:custom-face
+                                 #:defer
+                                 #:defines
+                                 #:demand
+                                 #:disabled
+                                 #:functions
+                                 #:hook
+                                 #:if
+                                 #:init
+                                 #:interpreter
+                                 #:load
+                                 #:load-path
+                                 #:magic
+                                 #:magic-fallback
+                                 #:mode
+                                 #:no-require
+                                 #:preface
+                                 #:requires
+                                 #:unless
+                                 #:when))
+
+  (define symbol->keyword*
+    (match-lambda
+      ((? symbol? kw)
+       (let* ((str (symbol->string kw))
+              (str* (if (string-prefix? ":" str)
+                        (string-drop str 1)
+                        str)))
+         (symbol->keyword (string->symbol str*))))
+      ((? keyword? kw)
+       kw)))
+
+  (define (use-package-keyword? obj)
+    (memq (symbol->keyword* obj) use-package-keywords))
+
+  (define extra-keyword->sexp
+    (match-lambda
+      (((? use-package-keyword? kw) . exps)
+       `(,(symbol->keyword* kw) ,@exps))
+      (_ #f)))
+
+  (match-record config <emacs-package>
+                (name
+                 extra-init-files
+                 load-force?
+                 load-predicates
+                 load-after-packages
+                 load-paths
+                 autoloads
+                 autoloads-interactive
+                 keys-global
+                 keys-global-keymaps
+                 keys-override
+                 keys-local
+                 options
+                 faces
+                 hooks
+                 auto-modes
+                 magic-modes
+                 extra-after-load
+                 extra-init
+                 extra-keywords)
+    (let* ((extra-init-files* (map emacs-package-extra-init-file->sexp
+                                   extra-init-files))
+           (load-predicates* (apply (compose-load-predicates-lambda 'and)
+                                    (list load-predicates)))
+           (load-after-packages* load-after-packages)
+           (autoloads* autoloads)
+           (autoloads-interactive* autoloads-interactive)
+           (load-paths* load-paths)
+           (keys-global+local (append keys-global
+                                      (append-map keys-local->sexp
+                                                  keys-local)))
+           (keys-global-keymaps* keys-global-keymaps)
+           (keys-override* keys-override)
+           (options* (map option->sexp options))
+           (faces* (map face->sexp faces))
+           (hooks* (map hook->sexp hooks))
+           (auto-modes* auto-modes)
+           (magic-modes* magic-modes)
+           (extra-after-load* extra-after-load)
+           (extra-init* (append extra-init
+                                (append-map keys-local->extra-sexps
+                                            keys-local)))
+           (extra-keywords* (apply append (filter-map extra-keyword->sexp
+                                                      extra-keywords)))
+           (comment-string (string-append ";;; Package "
+                                          (symbol->string name)
+                                          "\n"))
+           (combined-sexps (append
+                            ;; XXX: Load 'extra-init-files' before the
+                            ;; 'use-package' macro, so that variables defined
+                            ;; in the loaded files can be used in the
+                            ;; 'options' field.  If we put 'load' calls in the
+                            ;; 'use-package' ':init' keyword, any variables
+                            ;; from extra files referenced in 'options' will
+                            ;; not be bound.  This is because 'use-package'
+                            ;; sets variables from the ':custom' keyword
+                            ;; before evaluating ':init' s-expressions.
+                            extra-init-files*
+                            (list
+                             `(use-package
+                               ,name
+                               ,@(if load-force?
+                                     '(:demand t)
+                                     '(:defer t))
+                               ,@(unless-null load-after-packages*
+                                              `(:after ,load-after-packages*))
+                               ,@(unless-null load-paths*
+                                              `(:load-path ,load-paths*))
+                               ,@(unless-null autoloads*
+                                              `(:autoload ,autoloads*))
+                               ,@(unless-null autoloads-interactive*
+                                              `(:commands
+                                                ,autoloads-interactive*))
+                               ,@(unless-null keys-global+local
+                                              `(:bind ,keys-global+local))
+                               ,@(unless-null keys-override*
+                                              `(#:bind* ,keys-override*))
+                               ,@(unless-null keys-global-keymaps*
+                                              `(:bind-keymap
+                                                ,keys-global-keymaps*))
+                               ,@(unless-null hooks*
+                                              `(:hook ,hooks*))
+                               ,@(unless-null auto-modes*
+                                              `(:mode ,auto-modes*))
+                               ,@(unless-null magic-modes*
+                                              `(:magic ,magic-modes*))
+                               ,@(unless-null faces*
+                                              `(:custom-face
+                                                ,@(append (list
+                                                           (elisp
+                                                            (unelisp-newline)))
+                                                          faces*)))
+                               ,@(unless-null options*
+                                              `(:custom
+                                                ,@(append (list
+                                                           (elisp
+                                                            (unelisp-newline)))
+                                                          options*)))
+                               ,@(unless-null extra-after-load*
+                                              `(:config
+                                                ,@(append (list
+                                                           (elisp
+                                                            (unelisp-newline)))
+                                                          extra-after-load*)))
+                               ,@extra-keywords*
+                               ,@(unless-null extra-init*
+                                              `(:init
+                                                ,@(append (list
+                                                           (elisp
+                                                            (unelisp-newline)))
+                                                          extra-init*))))))))
+      (if (null? combined-sexps)
+          '()
+          (append
+           (list (elisp (unelisp-comment comment-string)))
+           (if (null? load-predicates*)
+               (map sexp->elisp combined-sexps)
+               (list (sexp->elisp `(when ,load-predicates*
+                                     ,@combined-sexps)))))))))
+
+(define %emacs-use-package-serializer
+  (emacs-package-serializer
+   (name 'emacs-use-package)
+   (procedure emacs-package->use-package-elisp)
+   ;; 'use-package' built-in to Emacs >=29.1.
+   (dependencies (list emacs-use-package))
+   (indent-forms '((use-package . 1)))
+   (extra-init
+    (list
+     (elisp (setq use-package-hook-name-suffix nil))))
+   (description "An Emacs package configuration serializer that configures
+Emacs with the @code{use-package} macro.")))
+
+(define-configuration/no-serialization home-emacs-packages-configuration
+  (package-serializer
+   (emacs-package-serializer %emacs-simple-package-serializer)
+   "The serializer to use for configuration specified by @code{emacs-package}
+objects.")
+  (packages
+   (list-of-emacs-packages '())
+   "A list of @code{emacs-package} objects containing configuration for Emacs
+packages."))
+
+(define* (merge-records original extensions accessor proc
+                        #:key (type? (const #t)) (= equal?))
+  "Extend list of records ORIGINAL with lists of records EXTENSIONS by merging
+all records where the field values of accessed by ACCESSOR are equal according
+to equality predicate = using PROC, a procedure that takes a record as its
+first argument and a list of records as its second argument and should return
+a single record object.  All objects that do not satisfy type predicate TYPE?
+are added to the returned list without comparison."
+  (let loop ((lst (apply append original extensions))
+             (acc '()))
+    (cond
+     ((null? lst) (reverse acc))
+     ((not (type? (car lst)))
+      (loop (cdr lst)
+            (cons (car lst) acc)))
+     ((partition
+       (lambda (ext)
+         (and (type? ext)
+              (= (apply accessor (list ext))
+                 (apply accessor (list (car lst))))))
+       (cdr lst))
+      (lambda (matches rest) (not (null? matches)))
+      => (lambda (matches rest)
+           (loop rest
+                 (cons (apply proc (car lst) (list matches))
+                       acc))))
+     (else (loop (cdr lst)
+                 (cons (car lst) acc))))))
+
+(define* (extend-default original extensions
+                         default field
+                         #:key (= eq?))
+  "Extend the value of ORIGINAL with any value in the list of EXTENSIONS that
+is not equal to the value of DEFAULT, signaling an error if there is any value
+in EXTENSIONS that is not equal to either ORIGINAL or DEFAULT according to
+equality predicate =, which defaults conservatively to 'eq?'.  For example, if
+DEFAULT and ORIGINAL are both #f, and at least one element of EXTENSIONS is
+#t, return #t, but if DEFAULT is 'foo', ORIGINAL is 'bar', and EXTENSIONS
+contains a value 'baz', then signal an error."
+  (let* ((new (fold (lambda (elem ret)
+                      (cond
+                       ((= elem original) ret)
+                       ((= elem default) elem)
+                       (else (configuration-field-error
+                              #f field elem))))
+                    '()
+                    extensions)))
+    (if (null? new) original new)))
+
+(define (extend-emacs-keymap orig exts)
+  "Extend <emacs-keymap> object ORIG with list of <emacs-keymap> objects
+EXTS."
+  (define %default-keymap (emacs-keymap))
+
+  (match-record orig <emacs-keymap>
+                (package-name
+                 repeat?
+                 repeat-exit
+                 repeat-enter
+                 disabled-commands
+                 keys)
+    (emacs-keymap
+     (inherit orig)
+     (package-name (extend-default package-name
+                                   (map emacs-keymap-package-name exts)
+                                   (emacs-keymap-package-name %default-keymap)
+                                   'package-name))
+     (repeat? (extend-default repeat?
+                              (map emacs-keymap-repeat? exts)
+                              (emacs-keymap-repeat? %default-keymap)
+                              'repeat?))
+     (repeat-exit (delete-duplicates (apply append
+                                            repeat-exit
+                                            (map emacs-keymap-repeat-exit exts))
+                                     equal?))
+     (repeat-enter (delete-duplicates (apply append
+                                             repeat-enter
+                                             (map emacs-keymap-repeat-enter exts))
+                                      equal?))
+     (disabled-commands (apply append
+                               disabled-commands
+                               (map emacs-keymap-disabled-commands exts)))
+     (keys (apply append keys (map emacs-keymap-keys exts))))))
+
+(define (merge-emacs-keymaps keymaps . rest)
+  "Merge list of <emacs-keymap> objects KEYMAPS with lists of <emacs-keymap>
+objects in REST."
+  (merge-records keymaps rest emacs-keymap-name extend-emacs-keymap))
+
+(define (extend-emacs-package orig exts)
+  "Extend <emacs-package> object ORIG with list of <emacs-package> objects
+EXTS."
+  (define %default-package (emacs-package))
+
+  (match-record orig <emacs-package>
+                (package
+                  extra-packages
+                  install?
+                  extra-init-files
+                  user-files
+                  load-force?
+                  load-predicates
+                  load-after-packages
+                  load-paths
+                  autoloads
+                  autoloads-interactive
+                  keys-global
+                  keys-global-keymaps
+                  keys-override
+                  keys-local
+                  options
+                  faces
+                  hooks
+                  auto-modes
+                  magic-modes
+                  extra-after-load
+                  extra-init
+                  extra-keywords)
+    (emacs-package
+      (inherit orig)
+      (package (extend-default package
+                               (map emacs-package-package exts)
+                               (emacs-package-package %default-package)
+                               'package))
+      (extra-packages (apply append
+                             extra-packages
+                             (map emacs-package-extra-packages exts)))
+      (install? (extend-default install?
+                                (map emacs-package-install? exts)
+                                (emacs-package-install? %default-package)
+                                'install?))
+      (extra-init-files (merge-alists extra-init-files
+                                      (append-map emacs-package-extra-init-files
+                                                  exts)))
+      (user-files (merge-alists user-files
+                                (append-map emacs-package-user-files
+                                            exts)))
+      (load-force? (extend-default load-force?
+                                   (map emacs-package-load-force? exts)
+                                   (emacs-package-load-force? %default-package)
+                                   'load-force?))
+      (load-predicates (apply append
+                              load-predicates
+                              (map emacs-package-load-predicates exts)))
+      (load-after-packages (delete-duplicates
+                            (apply append
+                                   load-predicates
+                                   (map emacs-package-load-predicates exts))
+                            eq?))
+      (load-paths (delete-duplicates
+                   (apply append
+                          load-paths
+                          (map emacs-package-load-paths exts))
+                   equal?))
+      (autoloads (delete-duplicates
+                  (apply append
+                         autoloads
+                         (map emacs-package-autoloads exts))
+                  eq?))
+      (autoloads-interactive (delete-duplicates
+                              (apply append
+                                     autoloads-interactive
+                                     (map emacs-package-autoloads-interactive exts))
+                              eq?))
+      (keys-global (apply append
+                          keys-global
+                          (map emacs-package-keys-global exts)))
+      (keys-global-keymaps (apply append
+                                  keys-global-keymaps
+                                  (map emacs-package-keys-global-keymaps exts)))
+      (keys-override (apply append
+                            keys-override
+                            (map emacs-package-keys-override exts)))
+      (keys-local (merge-records keys-local
+                                 (map emacs-package-keys-local exts)
+                                 emacs-keymap-name
+                                 extend-emacs-keymap))
+      (options (apply append options (map emacs-package-options exts)))
+      (faces (apply append faces (map emacs-package-faces exts)))
+      (hooks (apply append hooks (map emacs-package-hooks exts)))
+      (auto-modes (apply append
+                         auto-modes
+                         (map emacs-package-auto-modes exts)))
+      (magic-modes (apply append
+                          magic-modes
+                          (map emacs-package-magic-modes exts)))
+      (extra-after-load (apply append
+                               extra-after-load
+                               (map emacs-package-extra-after-load exts)))
+      (extra-init (apply append
+                         extra-init
+                         (map emacs-package-extra-init exts)))
+      (extra-keywords (merge-alists extra-keywords
+                                    (append-map emacs-package-extra-keywords
+                                                exts))))))
+
+(define (merge-emacs-packages packages . rest)
+  "Merge list of <emacs-package> objects PACKAGES with lists of
+<emacs-package> objects in REST.  Records with a non-null 'load-predicates'
+field will not be merged, which is useful, for example, when there are
+multiple different configurations for a package whose load predicates test
+conditions that can only be known at Emacs initialization time."
+  (define (emacs-package-no-predicates? config)
+    (match-record config <emacs-package>
+                  (load-predicates)
+      (null? load-predicates)))
+
+  (merge-records packages rest emacs-package-name extend-emacs-package
+                 #:type? emacs-package-no-predicates?))
+
+(define (emacs-packages->elisp packages serializer)
+  "Return a list of Elisp expressions containing the configuration for the
+list of <emacs-package> objects PACKAGES serialized according to SERIALIZER,
+an <emacs-package-serializer> object."
+
+  (define extra-init-files-load-dir-sexps
+    ;; Set an Elisp variable so that package serializers can use it to find
+    ;; extra init files.
+    (list `(setq ,%emacs-extra-init-files-path-variable
+                 ,%emacs-extra-init-files-path)))
+
+  (append extra-init-files-load-dir-sexps
+          (list (elisp (unelisp-newline)))
+          (emacs-package-serializer-extra-init serializer)
+          (list (elisp (unelisp-newline)))
+          ;; Multiple packages with the same name should already have been
+          ;; merged by 'home-emacs-packages-extensions'.
+          (apply append
+                 (interpose
+                  (map (emacs-package-serializer-procedure serializer)
+                       packages)
+                  (list (elisp (unelisp-newline)))
+                  'suffix))))
+
+(define (home-emacs-packages-configuration-extension config)
+  "Return a 'home-emacs-extension' from 'home-emacs-packages-configuration'
+CONFIG."
+  (define (emacs-package->installable-packages config)
+    (match-record config <emacs-package>
+                  (package extra-packages install?)
+      (if install?
+          (append (if (null? package)
+                      '()
+                      (list package))
+                  extra-packages)
+          '())))
+
+  (match-record config <home-emacs-packages-configuration>
+                (package-serializer
+                 packages)
+    ;; XXX: Store the old value of PACKAGES because the newly defined
+    ;; 'packages' field will overshadow its value when we try to reference it
+    ;; in the 'init-files' and 'user-files' field declarations.
+    (let ((packages* packages))
+      (home-emacs-extension
+       (packages (delete-duplicates
+                  (append
+                   (emacs-package-serializer-dependencies package-serializer)
+                   (append-map emacs-package->installable-packages packages))
+                  eq?))
+       (indent-forms (emacs-package-serializer-indent-forms package-serializer))
+       (init-file (emacs-packages->elisp packages* package-serializer))
+       ;; XXX: For user file entries with names matching those already
+       ;; declared for the home Emacs service type, their contents will be
+       ;; appended to the already existing entries.
+       (user-files (merge-alists (append-map emacs-package-user-files
+                                             packages*)))))))
+
+(define (home-emacs-packages-extra-files config)
+  "Return from <home-emacs-packages-configuration> CONFIG an association list
+of extra initialization files to create in XDG_CONFIG_HOME."
+  (match-record config <home-emacs-packages-configuration>
+                (packages)
+    (let ((created-files `(,%emacs-early-init-filename
+                           ,%emacs-user-init-filename)))
+      (append-map
+       (lambda (package)
+         (append-map (match-lambda
+                       ((name . files)
+                        ;; XXX: Some packages could still try to create files
+                        ;; with the same name.
+                        (when (member name created-files)
+                          (configuration-field-error #f 'extra-init-files
+                                                     (cons name files)))
+                        (set! created-files (cons name created-files))
+                        (list
+                         (list (emacs-config-filename name)
+                               (apply composite-file
+                                      (basename name)
+                                      (ensure-list files)))))
+                       (_ '()))
+                     (emacs-package-extra-init-files package)))
+       packages))))
+
+(define (home-emacs-packages-extensions original extension-packages)
+  "Extend the Emacs packages home service configuration ORIGINAL with list of
+extension packages EXTENSION-PACKAGES."
+  (match-record original <home-emacs-packages-configuration>
+                (packages)
+    (home-emacs-packages-configuration
+     (inherit original)
+     (packages (merge-emacs-packages (append packages extension-packages))))))
+
+(define home-emacs-packages-service-type
+  (service-type (name 'home-emacs-packages-service)
+                (extensions
+                 (list (service-extension
+                        home-emacs-service-type
+                        home-emacs-packages-configuration-extension)
+                       (service-extension
+                        home-xdg-configuration-files-service-type
+                        home-emacs-packages-extra-files)))
+                (default-value '())
+                (compose concatenate)
+                (extend home-emacs-packages-extensions)
+                (description
+                 "Configure packages for the GNU Emacs extensible text editor.")))
+
+
+;;;
+;;; Utility functions
+;;;
+
+(define (schemified-elisp->home-emacs-packages-configuration lst)
+  "Convert LST, a list of s-expressions, into Emacs configuration records.
+Returns a `home-emacs-packages-configuration' record. "
+  (define elisp->scheme
+    (match-lambda
+      ('t
+       #t)
+      ('nil
+       #f)
+      ((? constant? obj)
+       obj)
+      (('quote obj)
+       obj)
+      (obj
+       (sexp->elisp obj))))
+
+  (define (variable-specs->alist specs)
+    (let lp ((specs specs)
+             (acc '()))
+      (match specs
+        (()
+         (reverse! acc))
+        (((? blank?) . rest)
+         (lp rest acc))
+        ((var val . rest)
+         (lp rest (cons (cons var (elisp->scheme val))
+                        acc)))
+        (_
+         (raise (formatted-message (G_ "invalid `setq'/`setopt' in file")))))))
+
+  (define (use-package->emacs-package name body)
+    (define elisp-keyword?
+      (match-lambda
+        ((? symbol? obj)
+         (string-prefix? ":" (symbol->string obj)))
+        (_ #f)))
+
+    (define dotted-pair?
+      (match-lambda
+        ((head . (not (? pair?)))
+         #t)
+        (_ #f)))
+
+    (define list-of-dotted-pairs?
+      (list-of dotted-pair?))
+
+    (let lp ((lst body)
+             (package (emacs-package (name name))))
+      (match lst
+        (()
+         package)
+        (((? elisp-keyword? kw) . rest)
+         (receive (args rest)
+             (break elisp-keyword? rest)
+           (match kw
+             (':demand
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (load-force? (match (remove blank? args)
+                                   ('nil #f)
+                                   (_ #t))))))
+             (':defer
+              ;; 'load-force?' is false by default, and here we only set it to
+              ;; true when there is a non-nil ':demand' keyword.
+              (lp rest package))
+             ((or ':if ':when)
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (load-predicates (append (emacs-package-load-predicates
+                                              package)
+                                             (match (remove blank? args)
+                                               ((exp)
+                                                (list (elisp (unelisp exp))))
+                                               (_ '())))))))
+             (':unless
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (load-predicates (append (emacs-package-load-predicates
+                                              package)
+                                             (match (remove blank? args)
+                                               ((exp)
+                                                (list (elisp (not
+                                                              (unelisp exp)))))
+                                               (_ '())))))))
+             (':after
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (load-after-packages (append
+                                          (emacs-package-load-after-packages
+                                           package)
+                                          (match (remove blank? args)
+                                            (((':all
+                                               . (? list-of-symbols? lst)))
+                                             lst)
+                                            (((':any . rest))
+                                             ;; Ignore, because we can't
+                                             ;; guarantee equivalent behavior.
+                                             '())
+                                            ((? list-of-symbols? lst)
+                                             lst)
+                                            (((? list-of-symbols? lst))
+                                             lst)
+                                            (_ '())))))))
+             (':load-path
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (load-paths (append (emacs-package-load-paths package)
+                                        (filter string?
+                                                (match args
+                                                  (((? list? lst))
+                                                   lst)
+                                                  ((? list? lst)
+                                                   lst)
+                                                  (_ '()))))))))
+             (':autoload
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (autoloads (append (emacs-package-autoloads package)
+                                       (match (remove blank? args)
+                                         ((? list-of-symbols? lst)
+                                          lst)
+                                         (((? list-of-symbols? lst))
+                                          lst)
+                                         (_ '())))))))
+             (':commands
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (autoloads-interactive (append
+                                            (emacs-package-autoloads-interactive
+                                             package)
+                                            (match (remove blank? args)
+                                              ((? list-of-symbols? lst)
+                                               lst)
+                                              (((? list-of-symbols? lst))
+                                               lst)
+                                              (_ '())))))))
+             (':bind*
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (keys-override
+                     (append (emacs-package-keys-override package)
+                             (filter dotted-pair?
+                                     (match args
+                                       (((? list? lst))
+                                        lst)
+                                       (_ args))))))))
+             (':bind
+              (receive (global local)
+                  (break elisp-keyword? (match args
+                                          (((? list? lst))
+                                           lst)
+                                          (_ args)))
+                (lp rest
+                    (emacs-package
+                      (inherit package)
+                      (keys-global (append (emacs-package-keys-global package)
+                                           (filter dotted-pair? global)))
+                      (keys-local
+                       (append
+                        (emacs-package-keys-local package)
+                        (let lp/inner ((lst (remove blank? local))
+                                       (keymaps '()))
+                          (match lst
+                            ((':map (? symbol? kmap) . rest)
+                             (receive (kspecs rest)
+                                 (break (cut memq <> '(:map :repeat-map))
+                                        rest)
+                               (lp/inner rest
+                                         (append
+                                          keymaps
+                                          (list (emacs-keymap
+                                                 (name kmap)
+                                                 (keys (filter dotted-pair?
+                                                               kspecs))))))))
+                            ((':repeat-map (? symbol? kmap) . rest)
+                             (receive (kspecs rest)
+                                 (break (cut memq <> '(:map :repeat-map))
+                                        rest)
+                               (lp/inner rest
+                                         (append
+                                          keymaps
+                                          (list
+                                           (emacs-keymap
+                                            (name kmap)
+                                            (repeat? #t)
+                                            (repeat-exit
+                                             (filter-map
+                                              (match-lambda
+                                                (((? string-or-vector?)
+                                                  . (? symbol? sym))
+                                                 sym)
+                                                (_ #f))
+                                              (take-while
+                                               (negate (cut eq? <>
+                                                            ':continue))
+                                               (drop-while
+                                                (negate (cut eq? <>
+                                                             ':exit))
+                                                kspecs))))
+                                            (keys (filter dotted-pair?
+                                                          kspecs))))))))
+                            (_ keymaps)))))))))
+             (':bind-keymap
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (keys-global-keymaps (append
+                                          (emacs-package-keys-global-keymaps
+                                           package)
+                                          (filter dotted-pair?
+                                                  (match args
+                                                    (((? list? lst))
+                                                     lst)
+                                                    (_ args))))))))
+             (':custom
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (options (append (emacs-package-options package)
+                                     (filter-map
+                                      (match-lambda
+                                        ((var val . rest)
+                                         `(,var . ,(elisp->scheme val)))
+                                        (_ #f))
+                                      (match args
+                                        (((and ((? list?) . rest) lst))
+                                         ;; E.g., :custom ((foo bar))
+                                         lst)
+                                        (_ args))))))))
+             (':custom-face
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (faces (append (emacs-package-faces package)
+                                   (filter-map (match-lambda
+                                                 (((? symbol? face)
+                                                   ((? pair? spec) ..1))
+                                                  `(,face . ,spec))
+                                                 (_ #f))
+                                               args))))))
+             (':hook
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (hooks (append (emacs-package-hooks package)
+                                   (match args
+                                     ((((? list-of-symbols? hooks)
+                                        . (? symbol? func)))
+                                      (map (cut cons <> func)
+                                           hooks))
+                                     (((? list-of-dotted-pairs? lst))
+                                      (filter-map (match-lambda
+                                                    (((? symbol? hook)
+                                                      . (? symbol? func))
+                                                     (cons hook func))
+                                                    (_ #f))
+                                                  lst))
+                                     ((or (? list-of-symbols? hooks)
+                                          ((? list-of-symbols? hooks)))
+                                      (map
+                                       (cute cons <>
+                                             (symbol-append name '-mode))
+                                       hooks))
+                                     (_ '())))))))
+             (':mode
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (auto-modes (append (emacs-package-auto-modes package)
+                                        (match args
+                                          ((or ((? string? strings) ..1)
+                                               (((? string? strings) ..1)))
+                                           (map (cut cons <> name)
+                                                strings))
+                                          ((or ((? list-of-dotted-pairs? lst))
+                                               (? list-of-dotted-pairs? lst))
+                                           (filter
+                                            (match-lambda
+                                              (((? string?) . (? symbol?))
+                                               #t)
+                                              (_ #f))
+                                            lst))
+                                          (_ '())))))))
+             (':magic
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (magic-modes (append (emacs-package-magic-modes package)
+                                         (match args
+                                           ((or ((? string? strings) ..1)
+                                                (((? string? strings) ..1)))
+                                            (map (cut cons <> name)
+                                                 strings))
+                                           ((or ((? list-of-dotted-pairs? lst))
+                                                (? list-of-dotted-pairs? lst))
+                                            (filter
+                                             (match-lambda
+                                               (((? string?) . (? symbol?))
+                                                #t)
+                                               (_ #f))
+                                             lst))
+                                           (_ '())))))))
+             (':config
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (extra-after-load (append (emacs-package-extra-after-load
+                                               package)
+                                              (map sexp->elisp
+                                                   args))))))
+             (':init
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (extra-init (append (emacs-package-extra-init package)
+                                        (map sexp->elisp
+                                             args))))))
+             (kw
+              (lp rest
+                  (emacs-package
+                    (inherit package)
+                    (extra-keywords (append (emacs-package-extra-keywords
+                                             package)
+                                            (list
+                                             `(,kw . ,(map sexp->elisp
+                                                           args)))))))))))
+        (((? blank?) . rest)
+         (lp rest package))
+        (_ (raise (formatted-message
+                   (G_ "invalid `use-package' form in file")))))))
+
+  (let loop ((lst lst)
+             (sexps '())
+             (config (emacs-package
+                       (name 'emacs)))
+             (packages '()))
+    (match lst
+      (()
+       (home-emacs-packages-configuration
+        (packages (merge-emacs-packages
+                   (append
+                    (list
+                     (emacs-package
+                       (inherit config)
+                       (keys-local
+                        (merge-emacs-keymaps
+                         (emacs-package-keys-local config)))
+                       (extra-init
+                        (append
+                         (emacs-package-extra-init config)
+                         (map sexp->elisp (reverse sexps))))))
+                    (reverse packages))))))
+      ((((or 'setq 'setopt) . specs) . rest)
+       (loop rest
+             sexps
+             (emacs-package
+               (inherit config)
+               (options (append (emacs-package-options config)
+                                (variable-specs->alist specs))))
+             packages))
+      ((`(bind-key* ,(? string-or-vector? key)
+                    (,(or 'quote 'function) ,(? symbol? def)) . ,_)
+        . rest)
+       (loop rest
+             sexps
+             (emacs-package
+               (inherit config)
+               (keys-override (append (emacs-package-keys-override config)
+                                      (list (cons key def)))))
+             packages))
+      (((or `(global-set-key ,(? vector? key)
+                             (,(or 'quote 'function) ,(? symbol? def)))
+            `(global-set-key (kbd ,(? string? key))
+                             (,(or 'quote 'function) ,(? symbol? def)))
+            `(keymap-global-set ,(? string? key)
+                                (,(or 'quote 'function) ,(? symbol? def)))
+            `(bind-key ,(? string-or-vector? key)
+                       (,(or 'quote 'function) ,(? symbol? def)))
+            `(bind-key ,(? string-or-vector? key)
+                       (,(or 'quote 'function) ,(? symbol? def))
+                       ,(or `(quote global-map)
+                            'global-map)
+                       . ,_))
+        . rest)
+       (loop rest
+             sexps
+             (emacs-package
+               (inherit config)
+               (keys-global (append (emacs-package-keys-global config)
+                                    (list (cons key def)))))
+             packages))
+      (((or `(define-key ,(? symbol? kmap)
+               ,(? vector? key)
+               (,(or 'quote 'function) ,(? symbol? def)))
+            `(define-key ,(? symbol? kmap)
+               (kbd ,(? string? key))
+               (,(or 'quote 'function) ,(? symbol? def)))
+            `(keymap-set ,(? symbol? kmap)
+                         ,(? string? key)
+                         (,(or 'quote 'function) ,(? symbol? def)))
+            `(bind-key ,(? string-or-vector? key)
+                       (,(or 'quote 'function) ,(? symbol? def))
+                       ,(or `(quote ,kmap) kmap)
+                       . ,_))
+        . rest)
+       (loop rest
+             sexps
+             (emacs-package
+               (inherit config)
+               (keys-local (append (emacs-package-keys-local config)
+                                   (list (emacs-keymap
+                                          (name kmap)
+                                          (keys
+                                           (list (cons key def))))))))
+             packages))
+      ((`(add-hook (quote ,hook) (quote ,fun)) . rest)
+       (loop rest
+             sexps
+             (emacs-package
+               (inherit config)
+               (hooks (append (emacs-package-hooks config)
+                              (cons hook fun))))
+             packages))
+      (((or `(defface ,face
+               ,(and (((or 'default #t 't (? list?)) . (prop . rest))
+                      ..1)
+                     spec))
+            `(face-spec-set (quote ,face)
+                            ,(and (((or 'default #t 't (? list?))
+                                    . (prop . rest))
+                                   ..1)
+                                  spec)))
+        . rest)
+       (loop rest
+             sexps
+             (emacs-package
+               (inherit config)
+               (faces (append (emacs-package-faces config)
+                              (cons face spec))))
+             packages))
+      ((`(use-package ,(? symbol? package) . ,body) . rest)
+       (loop rest
+             sexps
+             config
+             (cons* (use-package->emacs-package package body)
+                    packages)))
+      (((? blank?) . rest)
+       (loop rest
+             sexps
+             config
+             packages))
+      ((exp . rest)
+       (loop rest
+             (cons (sexp->elisp exp) sexps)
+             config
+             packages)))))
+
+(define* (elisp->code exp #:optional sexp)
+  "Return Elisp EXP with blanks removed. When SEXP is non-nil, do not wrap in
+an 'elisp' form."
+  ;; Simple serialization for Elisp expressions containing no G-expressions or
+  ;; file-likes.
+  (let ((exp (fold-right/elisp (lambda (t s)
+                                 (match t
+                                   ((? vertical-space?)
+                                    '(unelisp-newline))
+                                   ((? page-break?)
+                                    '(unelisp-page-break))
+                                   ((? comment?)
+                                    `(unelisp-comment
+                                      ,(comment->string t)))
+                                   (_ t)))
+                               (lambda (t s)
+                                 (if (not t)
+                                     (list->dotted-list s)
+                                     s))
+                               cons
+                               '()
+                               exp)))
+    (if sexp
+        exp
+        `(elisp ,exp))))
+
+(define (home-emacs-packages-configuration->code config)
+  "Return a Scheme s-expression creating a 'home-emacs-packages-configuration'
+record equivalent to CONFIG."
+  (define-syntax unless-null
+    (syntax-rules ()
+      ((_ var exp)
+       (if (null? var)
+           '()
+           (list (list 'var exp))))))
+
+  (define (alist->code lst)
+    (list (if (any (match-lambda
+                     ((var . (? elisp? val))
+                      #t)
+                     (_ #f))
+                   lst)
+              'quasiquote
+              'quote)
+          (map (match-lambda
+                 ((var . (? elisp? val))
+                  ;; Works because `quasiquote' expands `unquote' forms like
+                  ;; `(a . ,C) correctly into (a . C), and
+                  ;; `pretty-print-elisp' prints them nicely.
+                  (cons var (list 'unquote
+                                  (elisp->code val))))
+                 ((var . val)
+                  (cons var val)))
+               lst)))
+
+  (define (emacs-keymap->code config)
+    (match-record config <emacs-keymap>
+                  (name
+                   repeat?
+                   repeat-exit
+                   repeat-enter
+                   disabled-commands
+                   keys)
+      `(emacs-keymap
+        (name (quote ,name))
+        ,@(if (not repeat?)
+              '()
+              (list `(repeat? ,repeat?)))
+        ,@(unless-null repeat-exit
+                       `(quote ,repeat-exit))
+        ,@(unless-null repeat-enter
+                       `(quote ,repeat-enter))
+        ,@(unless-null disabled-commands
+                       `(quote ,disabled-commands))
+        ,@(unless-null keys
+                       `(quote ,keys)))))
+
+  (define (emacs-package->code config)
+    (match-record config <emacs-package>
+                  (name
+                   load-force?
+                   load-predicates
+                   load-after-packages
+                   load-paths
+                   autoloads
+                   autoloads-interactive
+                   keys-global
+                   keys-global-keymaps
+                   keys-override
+                   keys-local
+                   options
+                   faces
+                   hooks
+                   auto-modes
+                   magic-modes
+                   extra-after-load
+                   extra-init
+                   extra-keywords)
+      `(emacs-package
+         (name (quote ,name))
+         ,@(if (not load-force?)
+               '()
+               (list `(load-force? ,load-force?)))
+         ,@(unless-null load-predicates
+                        `(list ,@(map elisp->code
+                                      load-predicates)))
+         ,@(unless-null load-after-packages
+                        `(quote ,load-after-packages))
+         ,@(unless-null load-paths
+                        `(quote ,(filter string?
+                                         load-paths)))
+         ,@(unless-null autoloads
+                        `(quote ,autoloads))
+         ,@(unless-null autoloads-interactive
+                        `(quote ,autoloads-interactive))
+         ,@(unless-null keys-global
+                        `(quote ,keys-global))
+         ,@(unless-null keys-global-keymaps
+                        `(quote ,keys-global-keymaps))
+         ,@(unless-null keys-override
+                        `(quote ,keys-override))
+         ,@(unless-null keys-local
+                        `(list ,@(map emacs-keymap->code
+                                      keys-local)))
+         ,@(unless-null options
+                        (alist->code options))
+         ,@(unless-null faces
+                        `(quote ,faces))
+         ,@(unless-null hooks
+                        `(quote ,hooks))
+         ,@(unless-null auto-modes
+                        `(quote ,auto-modes))
+         ,@(unless-null magic-modes
+                        `(quote ,magic-modes))
+         ,@(unless-null extra-after-load
+                        `(elisp* ,@(map (cut elisp->code <> #t)
+                                        extra-after-load)))
+         ,@(unless-null extra-init
+                        `(elisp* ,@(map (cut elisp->code <> #t)
+                                        extra-init)))
+         ,@(unless-null extra-keywords
+                        (list 'quasiquote
+                              (map (match-lambda
+                                     ((head . tail)
+                                      `(,head
+                                        ,@(map (lambda (e)
+                                                 (list 'unquote
+                                                       (elisp->code e)))
+                                               tail))))
+                                   extra-keywords))))))
+
+  (match-record config <home-emacs-packages-configuration>
+                (packages)
+    `(home-emacs-packages-configuration
+      (packages
+       ,(if (null? packages)
+            '(quote ())
+            `(list ,@(map emacs-package->code
+                          packages)))))))
+
+(define (read-elisp* port)
+  "Read Elisp from port."
+  ;; 'read-elisp' from (language elisp parser) supports the following
+  ;; character read syntaxes:
+  ;;
+  ;; ?X (supported), ?\uXXXX (supported), ?\uXXXXXXXX, (supported), ?\X
+  ;; (supported), ?\XXX (octal, supported)
+  ;;
+  ;; 'read-elisp' does not support any of the following character read
+  ;; syntaxes:
+  ;;
+  ;; ?\N{NAME} (returns same as ?\N), ?\N{U+X} (returns same as ?\N), or \xXX
+  ;; (unsupported, signals error)
+  (define (sanitize-quasiquote exp)
+    (fold-right/elisp (lambda (t s)
+                        (match t
+                          ;; XXX: Prevent `(a b . ,c) -> `(a b #{,} c)
+                          ('#{,}# 'unquote)
+                          ('nil #f)
+                          ('t #t)
+                          (_ t)))
+                      (lambda (t s)
+                        (let ((s* (if (not t)
+                                      (list->dotted-list s)
+                                      s)))
+                          (match s*
+                            (`(#{`}# . ,rest)
+                             (cons* 'quasiquote rest))
+                            (`(#{,}# . ,rest)
+                             (cons* 'unquote rest))
+                            (`(#{,@}# . ,rest)
+                             (cons* 'unquote-splicing rest))
+                            (_ s*))))
+                      cons
+                      '()
+                      exp))
+
+  (sanitize-quasiquote (read-elisp port)))
+
+(define (read-elisp/sequence port)
+  "Read Elisp from PORT until the end-of-file is reached and return the list
+of expressions that were read."
+  (let loop ((lst '()))
+    (match (read-elisp* port)
+      ((? eof-object?)
+       (reverse! lst))
+      (`(%set-lexical-binding-mode ,(or #f #t))
+       (loop lst))
+      (exp
+       (loop (cons exp lst))))))
+
+(define (input->home-emacs-packages-configuration port)
+  "Return a `home-emacs-packages-configuration' record from Elisp read from
+PORT."
+  (schemified-elisp->home-emacs-packages-configuration
+   (read-elisp/sequence port)))
+
+(define (input->home-emacs-packages-configuration-sexp port)
+  "Return an s-expression defining a `home-emacs-packages-configuration'
+record from Elisp read from PORT."
+  (home-emacs-packages-configuration->code
+   (input->home-emacs-packages-configuration port)))
+
+;;; emacs.scm ends here
diff --git a/gnu/local.mk b/gnu/local.mk
index 997b7344ff..98bb48d25c 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -71,6 +71,7 @@
 # Copyright © 2024 Runciter <runciter@whispers-vpn.org>
 # Copyright © 2024 Ashvith Shetty <ashvithshetty10@gmail.com>
 # Copyright © 2024 James Smith <jsubuntuxp@disroot.org>
+# Copyright © 2025 Kierin Bell <fernseed@fernseed.me>
 #
 # This file is part of GNU Guix.
 #
@@ -106,6 +107,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/home/services/desktop.scm			\
   %D%/home/services/dict.scm			\
   %D%/home/services/dotfiles.scm		\
+  %D%/home/services/emacs.scm			\
   %D%/home/services/symlink-manager.scm		\
   %D%/home/services/fontutils.scm		\
   %D%/home/services/gnupg.scm			\
diff --git a/guix/formatters/elisp.scm b/guix/formatters/elisp.scm
new file mode 100644
index 0000000000..032dd9e99e
--- /dev/null
+++ b/guix/formatters/elisp.scm
@@ -0,0 +1,918 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021-2023 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2023-2025 Kierin Bell <fernseed@fernseed.me>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix formatters elisp)
+  #:use-module (ice-9 control)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 vlist)
+  #:use-module (ice-9 format)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-35)
+  #:use-module (guix read-print)
+  #:use-module (guix i18n)
+  #:use-module ((guix diagnostics)
+                #:select (formatted-message
+                          &fix-hint &error-location
+                          location))
+  #:export (pretty-print-elisp
+            pretty-print-elisp/splice))
+
+;;; Commentary:
+;;;
+;;; Currently, this is adapted from the pretty printer in (guix read-print).
+;;; In the future, it would be better to implement this à la SRFI-159/166.
+;;;
+;;; Code:
+
+(define-syntax vhashq
+  (syntax-rules (quote)
+    ((_) vlist-null)
+    ((_ (key (quote (lst ...))) rest ...)
+     (vhash-consq key '(lst ...) (vhashq rest ...)))
+    ((_ (key value) rest ...)
+     (vhash-consq key '((() . value)) (vhashq rest ...)))))
+
+(define %elisp-special-forms
+  ;; Forms that should be indented specially in Elisp, adapted from the
+  ;; 'lisp-indent-function' property for each symbol by adding 1 to each
+  ;; integer value (and substituting 3 for `defun').  This is a non-exhaustive
+  ;; list, generated by mapping over the obarray of a minimal Emacs
+  ;; environment, and then removing symbols that are obsolete or unlikely to
+  ;; ever appear in an Emacs package or configuration file.
+  (vhashq
+   ('and-let* 2)
+   ('atomic-change-group 1)
+   ('autoload 3)
+   ('benchmark-progn 1)
+   ('benchmark-run 2)
+   ('benchmark-run-compiled 2)
+   ('byte-compile-maybe-guarded 2)
+   ('catch 2)
+   ('cl-block 2)
+   ('cl-callf 3)
+   ('cl-callf2 4)
+   ('cl-case 2)
+   ('cl-defgeneric 3)
+   ('cl-define-compiler-macro 3)
+   ('cl-defmacro 3)
+   ('cl-defmethod 4)
+   ('cl-defstruct 2)
+   ('cl-defsubst 3)
+   ('cl-deftype 3)
+   ('cl-defun 3)
+   ('cl-destructuring-bind 3)
+   ('cl-do 3)
+   ('cl-do* 3)
+   ('cl-do-all-symbols 2)
+   ('cl-do-symbols 2)
+   ('cl-dolist 2)
+   ('cl-dotimes 2)
+   ('cl-ecase 2)
+   ('cl-etypecase 2)
+   ('cl-eval-when 2)
+   ('cl-flet 2)
+   ('cl-flet* 2)
+   ('cl-generic-define-context-rewriter 4)
+   ('cl-generic-define-generalizer 2)
+   ('cl-iter-defun 3)
+   ('cl-labels 2)
+   ('cl-letf 2)
+   ('cl-letf* 2)
+   ('cl-macrolet 2)
+   ('cl-multiple-value-bind 3)
+   ('cl-multiple-value-setq 2)
+   ('cl-once-only 2)
+   ('cl-progv 3)
+   ('cl-return-from 2)
+   ('cl-symbol-macrolet 2)
+   ('cl-the 2)
+   ('cl-typecase 2)
+   ('cl-with-gensyms 2)
+   ('combine-after-change-calls 1)
+   ('combine-change-calls 3)
+   ('condition-case 3)
+   ('condition-case-unless-debug 3)
+   ('def-edebug-elem-spec 2)
+   ('def-edebug-spec 2)
+   ('defadvice 3)
+   ('defalias 3)
+   ('defclass 3)
+   ('defconst 3)
+   ('defcustom 3)
+   ('defface 3)
+   ('defgroup 3)
+   ('defimage 3)
+   ('define-abbrev 3)
+   ('define-abbrev-table 3)
+   ('define-advice 3)
+   ('define-alternatives 3)
+   ('define-auto-insert 3)
+   ('define-button-type 3)
+   ('define-category 3)
+   ('define-char-code-property 3)
+   ('define-derived-mode 3)
+   ('define-fringe-bitmap 3)
+   ('define-generic-mode 2)
+   ('define-globalized-minor-mode 3)
+   ('define-inline 3)
+   ('define-keymap 3)
+   ('define-mail-user-agent 3)
+   ('define-minor-mode 3)
+   ('define-multisession-variable 3)
+   ('define-obsolete-function-alias 3)
+   ('define-obsolete-variable-alias 3)
+   ('define-short-documentation-group 3)
+   ('define-skeleton 3)
+   ('define-widget 3)
+   ('define-widget-keywords 3)
+   ('defmacro 3)
+   ('defmath 3)
+   ('defsubst 3)
+   ('deftheme 2)
+   ('defun 3)
+   ('defvar 3)
+   ('defvar-keymap 2)
+   ('defvar-local 3)
+   ('defvaralias 3)
+   ('delay-mode-hooks 1)
+   ('dlet 2)
+   ('dolist 2)
+   ('dolist-with-progress-reporter 3)
+   ('dotimes 2)
+   ('dotimes-with-progress-reporter 3)
+   ('easy-menu-define 3)
+   ('easy-mmode-defmap 2)
+   ('easy-mmode-defsyntax 2)
+   ('ert-deftest 3)
+   ('eval-after-load 2)
+   ('eval-and-compile 1)
+   ('eval-when-compile 1)
+   ('gv-define-expander 2)
+   ('gv-define-setter 3)
+   ('gv-letplace 3)
+   ('if 2)                              ; Changed from 3
+   ('if-let 2)                          ; Changed from 3
+   ('if-let* 2)                         ; Changed from 3
+   ('ignore-error 2)
+   ('ignore-errors 1)
+   ('isearch-define-mode-toggle 4)
+   ('keymap-set-after 4)
+   ('lambda 2)                          ; Changed from 3
+   ('let 2)
+   ('let* 2)
+   ('let-alist 2)
+   ('let-when-compile 2)
+   ('letrec 2)
+   ('macroexp-let2 4)
+   ('macroexp-let2* 3)
+   ('minibuffer-with-setup-hook 2)
+   ('named-let 3)
+   ('oclosure-define 2)
+   ('oclosure-lambda 3)
+   ('pcase 2)
+   ('pcase-defmacro 3)
+   ('pcase-dolist 2)
+   ('pcase-exhaustive 2)
+   ('pcase-lambda 4)
+   ('pcase-let 2)
+   ('pcase-let* 2)
+   ('prog1 2)
+   ('prog2 3)
+   ('progn 1)
+   ('rx-define 3)
+   ('rx-let 2)
+   ('rx-let-eval 2)
+   ('save-current-buffer 1)
+   ('save-excursion 1)
+   ('save-mark-and-excursion 1)
+   ('save-match-data 1)
+   ('save-restriction 1)
+   ('save-selected-window 1)
+   ('save-window-excursion 1)
+   ('seq-doseq 2)
+   ('seq-let 3)
+   ('thread-first 1)
+   ('thread-last 1)
+   ('track-mouse 1)
+   ('unless 2)
+   ('unwind-protect 2)
+   ('use-package 2)                     ; Changed from 3
+   ('when 2)
+   ('when-let 2)
+   ('when-let* 2)
+   ('while 2)
+   ('while-let 2)
+   ('while-no-input 1)
+   ('with-auto-compression-mode 1)
+   ('with-buffer-unmodified-if-unchanged 1)
+   ('with-case-table 2)
+   ('with-category-table 2)
+   ('with-coding-priority 2)
+   ('with-current-buffer 2)
+   ('with-current-buffer-window 4)
+   ('with-decoded-time-value 2)
+   ('with-delayed-message 2)
+   ('with-demoted-errors 2)
+   ('with-displayed-buffer-window 4)
+   ('with-environment-variables 2)
+   ('with-eval-after-load 2)
+   ('with-existing-directory 1)
+   ('with-file-modes 2)
+   ('with-help-window 2)
+   ('with-local-quit 1)
+   ('with-locale-environment 2)
+   ('with-memoization 2)
+   ('with-minibuffer-completions-window 1)
+   ('with-minibuffer-selected-window 1)
+   ('with-mutex 2)
+   ('with-no-warnings 1)
+   ('with-output-to-string 1)
+   ('with-output-to-temp-buffer 2)
+   ('with-selected-frame 2)
+   ('with-selected-window 2)
+   ('with-silent-modifications 1)
+   ('with-slots 3)
+   ('with-suppressed-warnings 2)
+   ('with-syntax-table 2)
+   ('with-temp-buffer 1)
+   ('with-temp-buffer-window 4)
+   ('with-temp-file 2)
+   ('with-temp-message 2)
+   ('with-timeout 2)
+   ('with-undo-amalgamate 1)
+   ('with-window-non-dedicated 2)))
+
+(define (prefix? candidate lst)
+  "Return true if CANDIDATE is a prefix of LST."
+  (let loop ((candidate candidate)
+             (lst lst))
+    (match candidate
+      (() #t)
+      ((head1 . rest1)
+       (match lst
+         (() #f)
+         ((head2 . rest2)
+          (and (equal? head1 head2)
+               (loop rest1 rest2))))))))
+
+(define* (special-form-lead symbol context
+                            #:key
+                            (special-forms '()))
+  "If SYMBOL is an Elisp special form in the given CONTEXT, return its number
+of arguments; otherwise return #f.  CONTEXT is a stack of symbols lexically
+surrounding SYMBOL.  If SYMBOL is a key in the alist SPECIAL-FORMS, return the
+value of the first matching alist entry instead."
+  ;; A value N in SPECIAL-FORMS is equivalent to a value of N+1 in the
+  ;; '%elisp-special-forms' vhashe; this makes SPECIAL-FORMS similar to the
+  ;; 'lisp-indent-function' symbol property in Emacs and probably less
+  ;; confusing.
+  (or (assq-ref special-forms symbol)
+      (match (vhash-assq symbol %elisp-special-forms)
+        (#f #f)
+        ((_ . alist)
+         (any (match-lambda
+                ((prefix . level)
+                 (and (prefix? prefix context) (- level 1))))
+              alist)))))
+
+(define (escaped-string str)
+  "Return STR with backslashes and double quotes escaped.  Everything else, in
+particular newlines, is left as is."
+  (list->string
+   `(#\"
+     ,@(string-fold-right (lambda (chr lst)
+                            (match chr
+                              (#\" (cons* #\\ #\" lst))
+                              (#\\ (cons* #\\ #\\ lst))
+                              (_   (cons chr lst))))
+                          '()
+                          str)
+     #\")))
+
+(define %elisp-natural-whitespace-string-forms
+  ;; When a string has one of these forms as its parent, only double quotes
+  ;; and backslashes are escaped; newlines, tabs, etc. are left as-is.
+  '(defun defmacro))
+
+(define* (printed-string str context)
+  "Return the read syntax for STR depending on CONTEXT."
+  (define (preserve-newlines? str)
+    (and (> (string-length str) 40)
+         (string-index str #\newline)))
+
+  (match context
+    (()
+     (if (preserve-newlines? str)
+         (escaped-string str)
+         (object->string str)))
+    ((head . _)
+     (if (or (memq head %elisp-natural-whitespace-string-forms)
+             (preserve-newlines? str))
+         (escaped-string str)
+         (object->string str)))))
+
+(define (string-width str)
+  "Return the \"width\" of STR--i.e., the width of the longest line of STR."
+  (apply max (map string-length (string-split str #\newline))))
+
+(define %not-newline
+  (char-set-complement (char-set #\newline)))
+
+(define (print-multi-line-comment str indent port)
+  "Print to PORT STR as a multi-line comment, with INDENT spaces preceding
+each line except the first one (they're assumed to be already there)."
+  (let loop ((lst (string-tokenize str %not-newline)))
+    (match lst
+      (() #t)
+      ((last)
+       (display last port)
+       (newline port))
+      ((head tail ...)
+       (display head port)
+       (newline port)
+       (display (make-string indent #\space) port)
+       (loop tail)))))
+
+(define %elisp-special-symbol-chars
+  ;; Characters that need to be backslash-escaped within an Elisp symbol (see
+  ;; (elisp) Symbol Type).
+  (char-set-complement (char-set-union char-set:letter+digit
+                                       (char-set #\- #\+ #\= #\( #\/
+                                                 #\_ #\~ #\! #\@ #\$
+                                                 #\% #\^ #\& #\: #\<
+                                                 #\> #\{ #\} #\? #\*))))
+
+(define %elisp-confusable-number-symbols
+  ;; Symbols that must begin with a backslash in order to prevent them from
+  ;; being read as Elisp numbers.
+  (make-regexp (string-append
+                "(^[+-]?[0-9]+(\\.[0-9]*[eE]?(\\+NaN|\\+INF|[0-9]+)?)?$)"
+                "|(^[0-9]+[eE][0-9]+$)")))
+
+(define* (symbol->display-string symbol)
+  "Return the most appropriate representation of SYMBOL."
+  (let ((str (list->string
+              (string-fold-right
+               (lambda (chr lst)
+                 (if (char-set-contains? %elisp-special-symbol-chars chr)
+                     (cons* #\\ chr lst)
+                     (cons chr lst)))
+               '()
+               (symbol->string symbol)))))
+    (if (regexp-exec %elisp-confusable-number-symbols str)
+        (string-append "\\" str)
+        str)))
+
+(define %elisp-basic-chars
+  ;; Characters that can safely be specified using the Elisp character read
+  ;; syntax without backslash-escapes.
+  (char-set-union char-set:letter+digit
+                  (char-set #\~ #\! #\@ #\$ #\% #\^
+                            #\& #\* #\- #\_ #\= #\+
+                            #\{ #\} #\/ #\? #\< #\>)))
+
+(define %elisp-simple-escape-chars
+  ;; Whitespace, control, and other special characters that can be specified
+  ;; using the `?\X' Elisp read syntax, where X is a single character that has
+  ;; a special meaning.
+  (char-set #\alarm #\backspace #\tab #\newline #\vtab #\page #\return
+            #\esc #\space #\\ #\delete))
+
+(define (atom->elisp-string obj)
+  "Return a string representation of atom OBJ that is suitable for the Emacs
+Lisp reader.  Pairs and arrays should be serialized with 'pretty-print-elisp'
+instead."
+  (match obj
+    (#t
+     "t")
+    (() "()")
+    ((? nil?)
+     "nil")
+    ((? char?)
+     (cond
+      ((char-set-contains? %elisp-basic-chars obj)
+       (list->string (list #\? obj)))
+      ((char-set-contains? %elisp-simple-escape-chars obj)
+       (list->string (list #\? #\\ (case obj
+                                     ((#\alarm) #\a)
+                                     ((#\backspace) #\b)
+                                     ((#\tab) #\t)
+                                     ((#\newline) #\n)
+                                     ((#\vtab) #\v)
+                                     ((#\page) #\f)
+                                     ((#\return) #\r)
+                                     ((#\esc) #\e)
+                                     ((#\space) #\s)
+                                     ((#\\) #\\)
+                                     ((#\delete) #\d)))))
+      (else
+       (let ((num (char->integer obj)))
+         (if (<= num 65535)
+             (format #f "?\\u~4,'0x" num)
+             (format #f "?\\U~:@(~8,'0x~)" num))))))
+    ((? string?)
+     (printed-string obj '()))
+    ((? symbol?)
+     (symbol->display-string obj))
+    ((? keyword?)
+     (string-append ":" (symbol->display-string (keyword->symbol obj))))
+    ((? number? num)
+     (match num
+       ((? exact-integer?)
+        ;; E.g., 123
+        (object->string num))
+       ((? exact?)
+        ;; E.g., 1/2
+        (object->string (exact->inexact num)))
+       ((? rational?)
+        ;; E.g., 1.5
+        (object->string num))
+       ((? nan?)
+        ;; Not implemented by `read-elisp'.
+        "0.0e+NaN")
+       ((? inf?)
+        ;; Not implemented by `read-elisp'.
+        (if (negative? num)
+            "-1.0e+INF"
+            "1.0e+INF"))
+       (_
+        ;; Complex numbers
+        (raise
+         (formatted-message (G_ "cannot serialize complex number to Elisp: ~a")
+                            num)))))
+    (_
+     ;; Not an atom.
+     (raise
+      (formatted-message (G_ "Error serializing object to Elisp: ~a")
+                         obj)))))
+
+(define* (pretty-print-elisp port obj
+                             #:key
+                             (format-comment
+                              (lambda (comment indent) comment))
+                             (format-vertical-space identity)
+                             (indent 0)
+                             (max-width 78)
+                             (long-list 5)
+                             (special-forms '()))
+  "Pretty-print OBJ to PORT as Elisp, attempting to use at most MAX-WIDTH
+character columns and assuming the current column is INDENT.  Comments present
+in OBJ are included in the output.
+
+Lists longer than LONG-LIST are written as one element per line.  Comments are
+passed through FORMAT-COMMENT before being emitted; a useful value for
+FORMAT-COMMENT is 'canonicalize-comment'.  Vertical space is passed through
+FORMAT-VERTICAL-SPACE; a useful value of 'canonicalize-vertical-space'.
+
+To specify additional rules for special indentation, use SPECIAL-FORMS, an
+association list where each entry is a pair of the form (SYMBOL . INDENT).
+When SYMBOL occurs at the beginning of a list in OBJ, the first INDENT
+expressions after SYMBOL will be indented as arguments and the rest will be
+indented as body expressions.  Arguments that cannot be printed on the same
+line as SYMBOL will be indented 4 columns beyond the base indentation of the
+enclosing list, and body expressions will be indented 2 columns beyond the
+base indentation."
+  (define (read-syntax? obj)
+    (memq obj '(quote
+                quasiquote
+                unquote
+                unquote-splicing
+                ;; Elisp-only special forms:
+                function)))
+
+  (define (list-of-lists? head tail)
+    ;; Return true if HEAD and TAIL denote a list of lists--e.g., a list of
+    ;; 'let' bindings or an alist.
+    (match head
+      ((thing . _)
+       (and (not (read-syntax? thing))
+            (match tail
+              (((? pair?) . _)
+               #t)
+              (_ #f))))
+      (_ #f)))
+
+  (define list?*
+    (match-lambda
+      (((not (? read-syntax?)) . _)
+       #t)
+      (_ #f)))
+
+  (define (starts-with-line-comment? lst)
+    ;; Return true if LST starts with a line comment.
+    (match lst
+      ((x . _) (and (comment? x) (not (comment-margin? x))))
+      (_ #f)))
+
+  (define (array?* obj)
+    (and (array? obj)
+         (not (string? obj))))
+
+  (define (length* x)
+    ;; Return the length of list or dotted list X.
+    (let lp ((lst x)
+	     (len 0))
+      (match lst
+        (()
+         len)
+	((not (? pair?))
+	 (+ len 1))
+	((head . tail)
+	 (lp tail (+ len 1))))))
+
+  (define (dotted-list->list exp)
+    (let lp ((lst exp)
+	     (acc '()))
+      (match lst
+        (()
+         (reverse acc))
+	((not (? pair?))
+         (lp '() (cons lst acc)))
+	((head . tail)
+	 (lp tail (cons head acc))))))
+
+  (let loop ((indent indent)
+             (column indent)
+             (delimited? #t)
+             (context '())
+             (obj obj))
+    (define (print-sequence context indent column lst delimited?
+                            force-newline?)
+      (define dotted? (dotted-list? lst))
+      (define long?
+        ;; For lists that are function calls, omit heads from long list count,
+        ;; but include them for lists that aren't function calls.
+        (> (+ (length* lst)
+              (if (or dotted?
+                      (match context
+                        (((not (? symbol?)) . _)
+                         #t)
+                        ((_ 'quote . _)
+                         #t)
+                        (_ #f)))
+                  1 0))
+           long-list))
+
+      (let print ((lst lst)
+                  (first? #t)
+                  (delimited? delimited?)
+                  (column column)
+                  (unquote? #f)         ; End of list when, e.g., `(a b . ,c).
+                  (kw? #f))             ; Previous item was a keyword.
+        (cond
+         ((null? lst)
+          column)
+         ((blank? lst)
+          ;; Comments or whitespace cannot occur at the end of a dotted list.
+          column)
+         ((or unquote? (not (pair? lst)))
+          ;; End of improper list.
+          (let ((newline? (or long?
+                              (sequence-would-protrude?
+                               (+ column 2 (if unquote? 1 0))
+                               lst)
+                              (read-syntax-would-protrude?
+                               (+ column 2 (if unquote? 1 0))
+                               lst))))
+            (if newline?
+                (begin
+                  (newline port)
+                  (display (make-string indent #\space) port))
+                (display " " port))
+            (display ". " port)
+            (when unquote? (display "," port))
+            (let ((column (+ (if newline?
+                                 (+ indent 2)
+                                 (+ column 3))
+                             (if unquote? 1 0))))
+              (loop indent column #t context lst))))
+         (else
+          (match lst
+            (('unquote obj)
+             ;; A form like `(a b . ,OBJ) was expanded into (quasiquote (a b
+             ;; unquote OBJ)), which will still be properly expanded by
+             ;; `quasiquote' into (a b . OBJ).
+             (print obj #f #f column #t kw?))
+            ((item . tail)
+             (define kw-item?*
+               (cond
+                ((keyword? item) #t)
+                ((symbol? item) (string-prefix? ":" (symbol->string item)))
+                (else #f)))
+             (define newline?
+               ;; Insert a newline if ITEM is itself a list, or if TAIL is
+               ;; long, but only if ITEM is not the first item.  Also insert a
+               ;; newline before a keyword, and before a read syntax that
+               ;; would protrude.  We need to test before invocation of
+               ;; 'print-sequence' whether the first ITEM would protrude,
+               ;; since INDENT must then be less than usual.  We thread the
+               ;; results of that test to here with FORCE-NEWLINE?.
+               (or (and first? force-newline?)
+                   (and (or (list?* item)
+                            long?
+                            (read-syntax-would-protrude?
+                             (+ column 1) item)
+                            kw-item?*)
+                        (or dotted?     ; Newline after head of improper list.
+                            (not first?)
+                            (and first?
+                                 (match context
+                                   (((and (not (? symbol?))
+                                          (not (? keyword?))) . _)
+                                    ;; Allow newline before first item when
+                                    ;; head of list is not a symbol.
+                                    ;; E.g.:
+                                    ;; (use-package foo
+                                    ;;   :bind (("C-c f f" . foo) ;\n
+                                    ;;          :map foo-map
+                                    ;;          ("C-c f g" . foo-status)))
+                                    #t)
+                                   ((_ 'quote _ ...)
+                                    ;; E.g.:
+                                    ;; '(a ;\n
+                                    ;;   b)
+                                    #t)
+                                   (_ #f))))
+                        (not kw?)       ; Previous ITEM not a keyword.
+                        (not delimited?)
+                        (not (blank? item)))))
+
+             (when newline?
+               (newline port)
+               (display (make-string indent #\space) port))
+             (let ((column (if newline? indent column)))
+               (print tail
+                      #f
+                      (blank? item)
+                      (loop indent column
+                            (or newline? delimited?)
+                            context
+                            item)
+                      #f
+                      kw-item?*))))))))
+
+    (define (sequence-would-protrude? indent lst)
+      ;; Return true if elements of LST written at INDENT would protrude
+      ;; beyond MAX-WIDTH.  This is implemented as a cheap test with false
+      ;; negatives to avoid actually rendering all of LST.
+      (find (match-lambda
+              ((? string? str)
+               (>= (+ (string-width (printed-string str '()))
+                      2 indent)
+                   max-width))
+              ((? symbol? symbol)
+               (>= (+ (string-width (symbol->display-string symbol))
+                      indent)
+                   max-width))
+              ((? boolean?)
+               (>= (+ 2 indent) max-width))
+              (()
+               (>= (+ 2 indent) max-width))
+              (_                        ;don't know
+               #f))
+            (if (dotted-list? lst) (dotted-list->list lst) lst)))
+
+    (define (read-syntax-would-protrude? indent lst)
+      (match lst
+        ((or ((? read-syntax? syntax) exp)
+             (((? read-syntax? syntax) exp) . _))
+         (sequence-would-protrude? (+ indent (case syntax
+                                               ((quote) 1)
+                                               ((unquote) 1)
+                                               (else 2)))
+                                   exp))
+        (_ #f)))
+
+    (define (special-form-lead* head)
+      (special-form-lead head context
+                         #:special-forms special-forms))
+
+    (define (special-form? head)
+      (special-form-lead* head))
+
+    (match obj
+      ((? comment? comment)
+       (if (comment-margin? comment)
+           (begin
+             (display " " port)
+             (display (comment->string (format-comment comment indent))
+                      port))
+           (begin
+             ;; When already at the beginning of a line, for example because
+             ;; COMMENT follows a margin comment, no need to emit a newline.
+             (unless (= column indent)
+               (newline port)
+               (display (make-string indent #\space) port))
+             (print-multi-line-comment (comment->string
+                                        (format-comment comment indent))
+                                       indent port)))
+       (display (make-string indent #\space) port)
+       indent)
+      ((? vertical-space? space)
+       (unless delimited? (newline port))
+       (let loop ((i (vertical-space-height (format-vertical-space space))))
+         (unless (zero? i)
+           (newline port)
+           (loop (- i 1))))
+       (display (make-string indent #\space) port)
+       indent)
+      ((? page-break?)
+       (unless delimited? (newline port))
+       (display #\page port)
+       (newline port)
+       (display (make-string indent #\space) port)
+       indent)
+      (('quote lst)
+       (unless delimited? (display " " port))
+       (display "'" port)
+       (loop indent (+ column (if delimited? 1 2))
+             #t (cons 'quote context) lst))
+      (('quasiquote lst)
+       (unless delimited? (display " " port))
+       (display "`" port)
+       (loop indent (+ column (if delimited? 1 2)) #t context lst))
+      (('unquote lst)
+       (unless delimited? (display " " port))
+       (display "," port)
+       (loop indent (+ column (if delimited? 1 2)) #t context lst))
+      (('unquote-splicing lst)
+       (unless delimited? (display " " port))
+       (display ",@" port)
+       (loop indent (+ column (if delimited? 2 3)) #t context lst))
+      (('function lst)
+       (unless delimited? (display " " port))
+       (display "#'" port)
+       (loop indent (+ column (if delimited? 2 3)) #t context lst))
+      (((? special-form? head) arguments ...)
+       ;; Special-case 'let', 'lambda', 'modify-inputs', etc. so the second
+       ;; and following arguments are less indented.
+       (let* ((lead    (special-form-lead* head))
+              (context (cons head context))
+              (head    (symbol->display-string head))
+              (total   (length arguments))
+              (body    (drop arguments (min lead total))))
+         (unless delimited? (display " " port))
+         (display "(" port)
+         (display head port)
+         (unless (zero? lead)
+           (display " " port))
+
+         ;; Print the first LEAD arguments.
+         (let* ((indent (+ column 2 (if delimited? 0 1)))
+                (old-column column)
+                (column (+ column 1
+                                  (if (zero? lead) 0 1)
+                                  (if delimited? 0 1)
+                                  (string-length head)))
+                (initial-indent (+ old-column 4 (if delimited? 0 1))))
+           (define new-column
+             (let inner ((n lead)
+                         (arguments (take arguments (min lead total)))
+                         (column column)
+                         (newline? #f))
+               (if (zero? n)
+                   (if (null? body)     ; No newline when body is empty.
+                       column
+                       (begin
+                         (newline port)
+                         (display (make-string indent #\space) port)
+                         indent))
+                   (match arguments
+                     (() column)
+                     ((head . tail)
+                      (when newline?
+                        ;; Print a newline when previous argument was a list.
+                        (newline port)
+                        (display (make-string initial-indent #\space) port))
+                      (inner (- n 1) tail
+                             (loop initial-indent
+                                   (if newline? initial-indent column)
+                                   (or newline? (= n lead))
+                                   context
+                                   head)
+                             (list?* head)))))))
+
+           ;; Print the remaining arguments.
+           (let ((column (print-sequence
+                          context indent new-column
+                          body
+                          #t #f)))
+             (display ")" port)
+             (+ column 1)))))
+      ((? array?* obj)
+       ;; Vectors, arrays, bytevectors, bitvectors.
+       (let* ((lst (array->list obj))
+              (overflow? (>= column max-width))
+              (column (if overflow?
+                          (+ indent 1)
+                          (+ column (if delimited? 1 2)))))
+         (if overflow?
+             (begin
+               (newline port)
+               (display (make-string indent #\space) port))
+             (unless delimited? (display " " port)))
+         (display "[" port)
+         (let ((column (print-sequence context column column lst #t #f)))
+           (display "]" port)
+           (+ column 1))))
+      ((head . tail)
+       ;; Lists and improper lists.
+       (let* ((overflow? (>= column max-width))
+              (column (if overflow?
+                          (+ indent 1)
+                          (+ column (if delimited? 1 2))))
+              ;; Newline for 'let' bindings, alists, long lists of constants.
+              (newline? (or (and (not (null? tail))
+                                 (list-of-lists? head tail))
+                            (starts-with-line-comment? tail)))
+              (context (cons head context)))
+         (if overflow?
+             (begin
+               (newline port)
+               (display (make-string indent #\space) port))
+             (unless delimited? (display " " port)))
+         (display "(" port)
+
+         (let* ((new-column (loop column column #t context head))
+                (force-newline? (and (not newline?)
+                                     (or (read-syntax-would-protrude?
+                                          (+ new-column 1) tail)
+                                         (match tail
+                                           (((and lst
+                                                  ((not (? read-syntax?)) . _))
+                                             . _)
+                                            ;; Newline before initial list
+                                            ;; argument with long element(s).
+                                            (sequence-would-protrude?
+                                             (+ new-column 1) lst))
+                                           (_ #f)))))
+                (indent (if (or (>= new-column max-width)
+                                force-newline?
+                                newline?
+                                (not (symbol? head))
+                                (match context
+                                  ((_ 'quote _ ...)
+                                   #t)
+                                  (_ #f))
+                                (dotted-list? (cons head tail))
+                                (sequence-would-protrude?
+                                 (+ new-column 1) tail))
+                            column
+                            (+ new-column 1))))
+           (when newline?
+             ;; Insert a newline right after HEAD.
+             (newline port)
+             (display (make-string indent #\space) port))
+           (let ((column
+                  (print-sequence context indent
+                                  (if newline? indent new-column)
+                                  tail newline? force-newline?)))
+             (display ")" port)
+             (+ column 1)))))
+      (_
+       (let* ((str (cond ((string? obj)
+                          (printed-string obj context))
+                         ((symbol? obj) (symbol->display-string obj))
+                         (else (atom->elisp-string obj))))
+              (len (string-width str)))
+         (if (and (> (+ column 1 len) max-width)
+                  (not delimited?))
+             (begin
+               (newline port)
+               (display (make-string indent #\space) port)
+               (display str port)
+               (+ indent len))
+             (begin
+               (unless delimited? (display " " port))
+               (display str port)
+               (+ column (if delimited? 0 1) len))))))))
+
+(define* (pretty-print-elisp/splice port lst
+                                    #:rest rest)
+  "Write to PORT the expressions and blanks listed in LST."
+  (for-each (lambda (exp)
+              (apply pretty-print-elisp port exp rest)
+              (unless (blank? exp)
+                (newline port)))
+            lst))
+
+;;; elisp.scm ends here
diff --git a/guix/read-print.scm b/guix/read-print.scm
index e8caa2cfef..f5659716c2 100644
--- a/guix/read-print.scm
+++ b/guix/read-print.scm
@@ -374,6 +374,19 @@ (define %newline-forms
    ('set-xorg-configuration '())
    ('services '(home-environment))
    ('home-bash-configuration '(service))
+   ('home-emacs-packages-service-type '(service))
+   ('home-emacs-packages-configuration '(service))
+   ('packages '(home-emacs-packages-configuration))
+   ('list '(packages home-emacs-packages-configuration))
+   ('emacs-package '(list packages))
+   ('elisp* '())
+   ('extra-after-load '(emacs-package))
+   ('extra-init '(emacs-package))
+   ('keys-global '(emacs-package))
+   ('keys-local '(emacs-package))
+   ('list '(keys-local emacs-package))
+   ('emacs-keymap '(list keys-local emacs-package))
+   ('options '(emacs-package))
    ('introduction '(channel))))
 
 (define (prefix? candidate lst)
diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm
index 15b4bc9798..3e2a238372 100644
--- a/guix/scripts/home/import.scm
+++ b/guix/scripts/home/import.scm
@@ -28,6 +28,7 @@ (define-module (guix scripts home import)
   #:autoload   (guix scripts package) (manifest-entry-version-prefix)
   #:use-module (guix read-print)
   #:use-module (gnu packages)
+  #:use-module (gnu home services emacs)
   #:use-module (ice-9 match)
   #:use-module (ice-9 rdelim)
   #:use-module (ice-9 regex)
@@ -110,10 +111,32 @@ (define (generate-bash-configuration+modules destination-directory)
       (guix gexp)
       (gnu home services shells))))
 
+(define (generate-emacs-packages-configuration+modules destination-directory)
+  "Return a list containing a 'home-emacs-packages-service-type' definition
+and the necessary modules for the definition."
+  (let ((init-file (any (lambda (path)
+                          (let ((path* (string-append destination-directory
+                                                      "/" path)))
+                            (and (file-exists? path*)
+                                 path*)))
+                        (list ".emacs.el"
+                              ".emacs"
+                              ".emacs.d/init.el"
+                              ".config/emacs/init.el"))))
+    `((service home-emacs-packages-service-type
+               ,(call-with-input-file init-file
+                  (lambda (port)
+                    (input->home-emacs-packages-configuration-sexp port))))
+      (gnu home services emacs))))
+
 (define %files+configurations-alist
   `((".bashrc" . ,generate-bash-configuration+modules)
     (".bash_profile" . ,generate-bash-configuration+modules)
-    (".bash_logout" . ,generate-bash-configuration+modules)))
+    (".bash_logout" . ,generate-bash-configuration+modules)
+    (".emacs.el" . ,generate-emacs-packages-configuration+modules)
+    (".emacs" . ,generate-emacs-packages-configuration+modules)
+    (".emacs.d/init.el" . ,generate-emacs-packages-configuration+modules)
+    (".config/emacs/init.el" . ,generate-emacs-packages-configuration+modules)))
 
 (define (configurations+modules configuration-directory)
   "Return a list of procedures which when called, generate code for a home
diff --git a/tests/formatters/elisp.scm b/tests/formatters/elisp.scm
new file mode 100644
index 0000000000..044de74f84
--- /dev/null
+++ b/tests/formatters/elisp.scm
@@ -0,0 +1,168 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Kierin Bell <fernseed@fernseed.me>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (tests-formatters-elisp)
+  #:use-module (srfi srfi-64)
+  #:use-module (guix formatters elisp)
+  #:use-module (guix read-print)
+  #:use-module (gnu home services emacs))
+
+(define (read-elisp* . x)
+  (apply (@@ (gnu home services emacs) read-elisp*) x))
+
+(define-syntax-rule (test-pretty-print exp str args ...)
+  "Test pretty printed Elisp string from EXP against STR."
+  (test-equal str
+    (call-with-output-string
+      (lambda (port)
+        (pretty-print-elisp port exp args ...)))))
+
+(define-syntax-rule (test-pretty-print-roundtrip str args ...)
+  "Test equality after a round-trip where STR is passed to a modified
+'read-elisp' and the resulting sexp is passed to 'pretty-print-elisp'."
+  (test-equal str
+    (call-with-output-string
+      (lambda (port)
+        (let ((exp (call-with-input-string str
+                     read-elisp*)))
+          (pretty-print-elisp port exp args ...))))))
+
+
+(test-begin "pretty-print-elisp")
+
+;; Reading Elisp characters as Scheme characters not supported.
+(test-pretty-print #\a "?a")
+(test-pretty-print #\alarm "?\\a")
+(test-pretty-print #\x2014 "?\\u2014")
+
+;; Reading comments (and margin comments) not supported.
+(test-pretty-print (elisp->sexp
+                    (elisp (progn 'foo
+                                  (unelisp-comment ";; Comment\n")
+                                  'bar)))
+                   "(progn\n  'foo\n  ;; Comment\n  'bar)")
+
+(test-pretty-print-roundtrip "224")
+(test-pretty-print-roundtrip "224.5")
+(test-pretty-print-roundtrip "-224.5")
+(test-pretty-print-roundtrip "\"string\"")
+(test-pretty-print-roundtrip "symbol")
+(test-pretty-print-roundtrip "'quoted-symbol")
+(test-pretty-print-roundtrip "symbol\\.with\\,escapes")
+(test-pretty-print-roundtrip "123non-confusable-symbol")
+(test-pretty-print-roundtrip "\\123e0")
+(test-pretty-print-roundtrip ":keyword*")
+
+(test-pretty-print-roundtrip "(a b c)")
+(test-pretty-print-roundtrip "(a . b)")
+(test-pretty-print-roundtrip "(a b . c)")
+(test-pretty-print-roundtrip "`(a b ,c)")
+(test-pretty-print-roundtrip "`(a b . ,c)")
+(test-pretty-print-roundtrip "(a b 'c)")
+
+(test-pretty-print-roundtrip "\
+(foo arg1
+     #'longer-than
+     arg3 arg4)"
+                         #:max-width 15)
+(test-pretty-print-roundtrip "\
+(foo
+ (list
+  longer)
+ b c)"
+                         #:max-width 10)
+(test-pretty-print-roundtrip "\
+(foo
+ #'longer-than
+ arg1 arg2)"
+                         #:max-width 10)
+(test-pretty-print-roundtrip "\
+(a
+ #'longer-than
+ b . c)"
+                         #:max-width 10)
+
+(test-pretty-print-roundtrip "\
+(defun foo (x y)
+  (let ((z (+ x y)))
+    (* z z)))")
+
+(test-pretty-print-roundtrip "[a b c]")
+(test-pretty-print-roundtrip "\
+[long-symbol
+ b c d]"
+                             #:max-width 10)
+(test-pretty-print-roundtrip "\
+[(long
+  list xx)
+ b c d]"
+                             #:max-width 10)
+
+(test-pretty-print-roundtrip "\
+(defun foo ()
+  (dlet ((x '((a b . \"c\"))))
+    x))")
+
+(test-pretty-print-roundtrip "\
+(defvar foo value)")
+
+(test-pretty-print-roundtrip "\
+(defvar foo #'foo-function
+  \"Foo function.\")")
+
+(test-pretty-print-roundtrip "\
+(if (fboundp 'foo-function)
+  (ding)
+  (autoload #'foo-function \"foo\"
+    \"Return foo.\"))")
+
+(test-pretty-print-roundtrip "\
+(long-variable-name-with-function-value
+ #'long-function-name-with-hash-read-syntax)"
+                             #:max-width 78)
+(test-pretty-print-roundtrip "\
+(a b
+   c
+   d
+   e
+   f
+   g)"
+                             #:long-list 5)
+(test-pretty-print-roundtrip "\
+[a
+ b
+ c
+ d
+ e
+ f]"
+                             #:long-list 5)
+(test-pretty-print-roundtrip "\
+(use-package foo
+  :bind ((\"C-c n\" . foo))
+  :custom (foo-bar 'bar)
+  (foo-baz my--baz)
+  :init (ding))"
+                             #:special-forms '((use-package . 1)))
+(test-pretty-print-roundtrip "\
+(with-current-buffer-window (setq buf
+                                  (get-buffer-create buf-name))
+    (cd-absolute directory)
+    (call-process-shell-command \"ls -l | sort -t _ -k 2\" nil t)
+  (dired-virtual directory))")
+
+(test-end "pretty-print-elisp")
diff --git a/tests/home/services/emacs.scm b/tests/home/services/emacs.scm
new file mode 100644
index 0000000000..159128236a
--- /dev/null
+++ b/tests/home/services/emacs.scm
@@ -0,0 +1,306 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2023 Kierin Bell <fernseed@fernseed.me>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (tests home services emacs)
+  #:use-module (gnu home services emacs)
+  #:use-module (guix store)
+  #:use-module (guix monads)
+  #:use-module (guix gexp)
+  #:use-module (guix derivations)
+  #:use-module (guix packages)
+  #:use-module (guix tests)
+  #:use-module (guix read-print)
+  #:use-module (gnu packages guile)
+  #:use-module (ice-9 textual-ports)
+  #:use-module (srfi srfi-64))
+
+(test-begin "emacs-home-services")
+
+;;; Test `elisp' syntax
+
+(test-equal "test `elisp' syntax, symbol"
+  't
+  (elisp->sexp (elisp t)))
+
+(test-equal "test `elisp' syntax, basic list"
+  '(a b c)
+  (elisp->sexp (elisp (a b c))))
+
+(test-equal "test `elisp' syntax, substitute symbol"
+  'a
+  (let ((foo 'a))
+    (elisp->sexp (elisp (unelisp foo)))))
+
+(test-equal "test `elisp' syntax, substitute splicing"
+  '(a b c)
+  (let ((foo '(a b c)))
+    (elisp->sexp (elisp ((unelisp-splicing foo))))))
+
+(test-equal "test `elisp' syntax, comment"
+  (comment ";comment\n")
+  (elisp->sexp (elisp (unelisp-comment ";comment\n"))))
+
+(test-equal "test `elisp' syntax, nested comment"
+  `(a ,(comment ";comment\n") b)
+  (elisp->sexp (elisp (a (unelisp-comment ";comment\n") b))))
+
+(test-equal "test `elisp' syntax, newline"
+  (vertical-space 0)
+  (elisp->sexp (elisp (unelisp-newline))))
+
+(test-equal "test `elisp' syntax, page break"
+  (page-break)
+  (elisp->sexp (elisp (unelisp-page-break))))
+
+(test-equal "elisp->sexp, nested <elisp> objects"
+  '(a (b c))
+  (elisp->sexp (elisp (a (unelisp (elisp (b (unelisp (elisp c)))))))))
+
+;;; Test Home import utility functions
+
+(define (home-emacs-packages-configuration->code . x)
+  (apply (@@ (gnu home services emacs)
+             home-emacs-packages-configuration->code) x))
+
+(define-syntax-rule (test-import-emacs-configuration str config)
+  "Test equality of 'home-emacs-packages-configuration' generated from Elisp
+string STR with record CONFIG"
+  (test-equal "Test Emacs packages Home configuration import"
+    (home-emacs-packages-configuration->code
+     (call-with-input-string str
+       input->home-emacs-packages-configuration))
+    (home-emacs-packages-configuration->code config)))
+
+(test-import-emacs-configuration
+ "(setq my--foo 1)
+(setq my--bar 'symbol)
+(setq my--baz (list 'a ;comment
+		    'b 'c))
+(setq my--quux '(a b . c))
+(setq my--quuux #'my--fun)
+(setopt foo-var my--foo)
+
+(foo-mode -1)
+(bar-mode 1)
+(baz-mode)
+(quux-mode my--foo)
+
+
+(bind-key* \"M-<up>\" 'scroll-down-line)
+(bind-key* \"M-<down>\" 'scroll-up-line t)
+;;; Top-level comment
+(global-set-key (kbd \"C-c b\") 'bar)
+(global-set-key [remap bar] 'baz)
+(keymap-global-set \"C-c v\" 'quux)
+(bind-key \"C-c c\" 'quuux)
+(bind-key [t] #'quuuux 'foo-map t)
+
+(use-package foo
+  :demand t
+  ;; Inconvenient comment
+  :hook prog-mode
+  :custom
+  (foo-bar 'baz)
+  (foo-baz baz)
+  :init
+  ;; Ding
+  (ding)
+
+  (message \"Ding\"))
+
+(use-package bar
+  :if (eq system-type 'gnu/linx)
+  :after foo
+  :load-path \"~/src/bar\"
+  :autoload bar-internal
+  :commands bar-status bar
+  :bind* ((\"C-x n\" . bar-status))
+  :bind ((\"C-c n\" . bar)
+	 :map bar-mode
+	 (\"C-@\" . bar-bar)
+	 :map bar-status-mode
+	 (\"C-n\" . bar-next)
+	 (\"C-c C-c\" . bar-do)
+	 :repeat-map bar-repeat-map
+	 (\"n\" . bar-next)
+	 (\"c\" . bar-do))
+  :bind-keymap (\"C-c b\" . bar-mode-map)
+  :custom
+  (bar-bool t)
+  (bar-string \"bar\")
+  (bar-list '(bar-1 bar-2 bar-3))
+  (bar-list-2 `(,@bar-list bar-4))
+  (bar-var my--foo)
+  :custom-face
+  (bar-face ((t (:slant italic))))
+  (bar-highlight-face ((((class color) (background light))
+			:background \"goldenrod1\")
+		       (((class color) (background dark))
+			:background \"DarkGoldenrod4\")
+		       (t :inverse-video t)))
+  :hook ((prog-mode foo-mode) . bar-mode)
+  :mode \"\\\\.bar\\\\'\"
+  :magic \">>BAR<<\"
+  :magic-fallback \"<<BAR>>\"
+  :config
+  ;; Extra configuration
+  (add-to-list 'bar-extensions 'foo-bar)
+  :catch (lambda (_ _)
+	   (message \"Error package initialization\")))
+
+(use-package baz
+  :unless (eq system-name \"bar\")
+  :after (foo bar)
+  :load-path (\"~/src/my/baz\" \"~/src/baz\")
+  :autoload (baz-1 baz-2)
+  :commands (baz)
+  :custom
+  ((baz-option t)
+   (bar-list '((baz-1 . baz-2)))
+   (baz-var my--foo))
+  :hook ((prog-mode . baz-mode)
+	 (bar-mode . baz-mode))
+  :mode (\"\\\\.baz\\\\'\" . baz-mode)
+  :magic (\">>BAZ<<\" \"!XXBAZXX\"))
+
+(defun my--fun-1 (arg)
+  arg)
+
+(defun my--fun ()
+  (prog1 (my--fun-1 'foo)
+    (ding)))
+"
+ (home-emacs-packages-configuration
+  (packages
+   (list
+    (emacs-package
+      (name 'emacs)
+      (keys-global
+       '(("C-c b" . bar)
+         (#(remap bar) . baz)
+         ("C-c v" . quux)
+         ("C-c c" . quuux)))
+      (keys-override
+       '(("M-<up>" . scroll-down-line)
+         ("M-<down>" . scroll-up-line)))
+      (keys-local
+       (list
+        (emacs-keymap
+         (name 'foo-map)
+         (keys '((#(t) . quuuux))))))
+      (options
+       `((my--foo . 1)
+         (my--bar . symbol)
+         (my--baz
+          . ,(elisp
+              (list 'a 'b 'c)))
+         (my--quux . (a b . c))
+         (my--quuux . ,(elisp (function my--fun)))
+         (foo-var . ,(elisp my--foo))))
+      (extra-init
+       (elisp*
+        (foo-mode -1)
+        (bar-mode 1)
+        (baz-mode)
+        (quux-mode my--foo)
+        (defun my--fun-1 (arg) arg)
+        (defun my--fun ()
+          (prog1 (my--fun-1 (quote foo)) (ding))))))
+    (emacs-package
+      (name 'foo)
+      (load-force? #t)
+      (options
+       `((foo-bar . baz)
+         (foo-baz . ,(elisp baz))))
+      (hooks '((prog-mode . foo-mode)))
+      (extra-init
+       (elisp*
+        (ding)
+        (message "Ding"))))
+    (emacs-package
+      (name 'bar)
+      (load-predicates
+       (list (elisp (eq system-type 'gnu/linx))))
+      (load-after-packages '(foo))
+      (load-paths '("~/src/bar"))
+      (autoloads '(bar-internal))
+      (autoloads-interactive '(bar-status bar))
+      (keys-global '(("C-c n" . bar)))
+      (keys-global-keymaps '(("C-c b" . bar-mode-map)))
+      (keys-override '(("C-x n" . bar-status)))
+      (keys-local
+       (list
+        (emacs-keymap
+         (name 'bar-mode)
+         (keys '(("C-@" . bar-bar))))
+        (emacs-keymap
+         (name 'bar-status-mode)
+         (keys '(("C-n" . bar-next)
+                 ("C-c C-c" . bar-do))))
+        (emacs-keymap
+         (name 'bar-repeat-map)
+         (repeat? #t)
+         (keys '(("n" . bar-next)
+                 ("c" . bar-do))))))
+      (options
+       `((bar-bool . #t)
+         (bar-string . "bar")
+         (bar-list . (bar-1 bar-2 bar-3))
+         (bar-list-2 . ,(elisp
+                         `(,@bar-list bar-4)))
+         (bar-var . ,(elisp my--foo))))
+      (faces
+       '((bar-face (t (:slant italic)))
+         (bar-highlight-face
+          . ((((class color) (background light))
+              :background "goldenrod1")
+             (((class color) (background dark))
+              :background "DarkGoldenrod4")
+             (t :inverse-video t)))))
+      (hooks '((prog-mode . bar-mode)
+               (foo-mode . bar-mode)))
+      (auto-modes '(("\\.bar\\'" . bar)))
+      (magic-modes '((">>BAR<<" . bar)))
+      (extra-after-load
+       (elisp*
+        (add-to-list 'bar-extensions 'foo-bar)))
+      (extra-keywords
+       `((:magic-fallback . (,(elisp "<<BAR>>")))
+         (:catch . (,(elisp
+                      (lambda (_ _)
+                        (message
+                         "Error package initialization"))))))))
+    (emacs-package
+      (name 'baz)
+      (load-predicates
+       (list (elisp (not (eq system-name "bar")))))
+      (load-after-packages '(foo bar))
+      (load-paths '("~/src/my/baz" "~/src/baz"))
+      (autoloads '(baz-1 baz-2))
+      (autoloads-interactive '(baz))
+      (options
+       `((baz-option . #t)
+         (bar-list . ((baz-1 . baz-2)))
+         (baz-var . ,(elisp my--foo))))
+      (hooks '((prog-mode . baz-mode)
+               (bar-mode . baz-mode)))
+      (auto-modes '(("\\.baz\\'" . baz-mode)))
+      (magic-modes '((">>BAZ<<" . baz)
+                     ("!XXBAZXX" . baz))))))))
+
+(test-end "emacs-home-services")
